Linux 多线程同步机制(上)

简介: Linux 多线程同步机制(上)

前言


一、线程同步

在多线程环境下,多个线程可以并发地执行,访问共享资源(如内存变量、文件、网络连接  等)。
这可能导致 数据不一致性, 死锁, 竞争条件等 问题。
为了解决这些问题,需要使用同步机制来确保线程间的协作和互斥访问共享资源。

“同步” 的目的 是为了避免数据的混乱,解决与时间有关的错误。实际上,不仅线程需要同步,进程间,信号间等等都需要同步机制

线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时 其他线程为保证数据的一致性,不能调用该功能。

二、互斥量 mutex

互斥锁(Mutex,全称为 Mutual Exclusion)是一种常用的同步机制,用于保护共享资源免受多个线程同时访问和修改的影响。互斥锁提供了一种互斥访问的机制,同一时间只允许一个线程获取锁并访问被保护的资源。

每个线程在对资源操作前都尝试进行先加锁,成功加锁才能操作,操作结束解锁。

资源还是共享的,线程也还是竞争的。

但 通过 “锁” 就将资源的访问变成互斥操作,而后与时间有关的错误也就不会再产生了。

1. 互斥锁的基本操作包括两个关键操作:

  • 加锁(Lock):线程通过申请互斥锁来获取对共享资源的访问权。如果互斥锁当前未被其他线程获取,线程成功获得锁然后进入临界区(Critical Section),可以访问共享资源。如果互斥锁已经被其他线程获取,申请锁的线程将被阻塞,直到锁被释放。
  • 解锁(Unlock):线程在完成对共享资源的访问之后,释放互斥锁,使得其他线程可以申请并获取锁。

2. 互斥锁的主要应用函数 :

pthread_mutex_init: 用于初始化互斥锁变量。

pthread_mutex_destroy: 用于销毁互斥锁对象。

pthread_mutex_lock: 用于加锁,如果互斥锁已被其他线程占用,则当前线程阻塞。

pthread_mutex_trylock: 尝试加锁,如果互斥锁已被其他线程占用,则返回一个失败状态而不阻塞线程。

pthread_mutex_unlock: 用于解锁,释放互斥锁使其他线程可以获取。

3. 初始化线程锁 :

有两种方式可以对互斥锁进行初始化:静态初始化和动态初始化。

  • 静态初始化: 是在定义互斥锁变量时直接进行初始化,不需要调用特定的初始化函数。
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化互斥锁变量。
  • 动态初始化:动态初始化是在运行时使用初始化函数对互斥锁进行初始化。
    pthread_mutex_init(&mutex, NULL);

4. 示例代码

在下面代码中,main 函数中有一个主线程 打印小写字母,my_thread 为 子线程 打印 大写字母。两个线程通过互斥锁来访问 共享资源。

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
pthread_mutex_t lock;             // 创建 互斥锁
void *my_thread(void *arg)
{
  srand(time(NULL));              // 设置随机种子
  while(1)
  {
    pthread_mutex_lock(&lock);
    printf("ABC ");
    sleep(rand() % 3);
    printf("XYZ\n");
    pthread_mutex_unlock(&lock);
    sleep(rand() % 3);              // 休眠随机秒,释放cpu资源
  }
  pthread_exit(NULL);
}
int main(void)
{
  pthread_t tid;
  int ret;
  srand(time(NULL));                  // 设置随机种子
  ret = pthread_mutex_init(&lock,  NULL);       // 初始化互斥锁
  if(ret != 0)
  {
    printf("pthread_mutex_init err\n");
  }
  ret = pthread_create(&tid, NULL, my_thread, NULL);
  if(ret != 0)
  {
    printf("pthread_create err\n");
  }
  while(1)
  {
    pthread_mutex_lock(&lock);
    printf("abc ");
    sleep(rand() % 3);
    printf("xyz\n");
    pthread_mutex_unlock(&lock);
    sleep(rand() % 3);
  }
  pthread_mutex_destroy(&lock);         // 销毁 互斥锁
  pthread_join(tid,NULL);             // 等待回收线程,获取回收状态
  return 0;
}

注意 :

锁粒度(Lock Granularity):锁的粒度应该尽可能小,以避免锁定过长时间,从而降低了并发性能。

三、死锁

死锁产生的原因:死锁是指多个线程或进程因为彼此相互等待对方所持有的资源而无法继续执行的状态。

解决:

  1. 使用资源的有序性:通过规定线程获取资源的顺序,避免出现循环等待的情况。例如,可以约定所有线程按照一定的顺序获取资源,从而避免死锁的发生。

如果下面两个线程 获取资源的顺序是相反的,则可能会产生死锁。可以将 线程 B 先获取 m1锁,再获取 m2锁。

以下面代码的方式获取锁,不会存在死锁风险。

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
void *my_thread1(void *arg)
{
  pthread_mutex_lock(&lock1);
  printf("my_thread1 : begin\n");
  pthread_mutex_lock(&lock2);
  printf("my_thread1 : end\n");
  pthread_mutex_unlock(&lock2);
  pthread_mutex_unlock(&lock1);
  pthread_exit(NULL);
}
void *my_thread2(void *arg)
{
  pthread_mutex_lock(&lock1);
  printf("my_thread2 : begin\n");
  pthread_mutex_lock(&lock2);
  printf("my_thread2 : end\n");
  pthread_mutex_unlock(&lock2);
  pthread_mutex_unlock(&lock1);
  pthread_exit(NULL);
}
int main(void)
{
  pthread_t tid1,tid2;
  int ret;
  ret = pthread_create(&tid1, NULL, my_thread1, NULL);
  if(ret != 0)
  {
    printf("pthread1_create err\n");
  }
  ret = pthread_create(&tid2, NULL, my_thread2, NULL);
  if(ret != 0)
  {
    printf("pthread2_create err\n");
  }
  pthread_join(tid1,NULL);
  pthread_join(tid2,NULL);
  return 0;
}
  1. 设置超时机制:在请求资源时,设置一个超时时间,在超过该时间后如果仍未获得资源,则放弃等待,释放已经获取的资源,避免长时间的死锁等待。

总结

相关文章
|
1月前
|
编解码 数据安全/隐私保护 计算机视觉
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
如何使用OpenCV进行同步和异步操作来打开海康摄像头,并提供了相关的代码示例。
85 1
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
|
9天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
28 5
|
12天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
29 6
|
19天前
|
消息中间件 存储 Linux
|
22天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
47 2
|
21天前
|
Java 调度
Java 线程同步的四种方式,最全详解,建议收藏!
本文详细解析了Java线程同步的四种方式:synchronized关键字、ReentrantLock、原子变量和ThreadLocal,通过实例代码和对比分析,帮助你深入理解线程同步机制。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 线程同步的四种方式,最全详解,建议收藏!
|
26天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1
|
26天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
25 1
|
1月前
|
安全 Java 开发者
在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制
【10月更文挑战第3天】在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制,如`synchronized`关键字、`Lock`接口及其实现类(如`ReentrantLock`),还有原子变量(如`AtomicInteger`)。这些工具可以帮助开发者避免数据不一致、死锁和活锁等问题。通过合理选择和使用这些机制,可以有效管理并发,确保程序稳定运行。例如,`synchronized`可确保同一时间只有一个线程访问共享资源;`Lock`提供更灵活的锁定方式;原子变量则利用硬件指令实现无锁操作。
20 2
|
1月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
29 0
Linux C/C++之线程基础