一,互斥锁

1.概念

互斥锁是并发编程中用于保护“共享资源”的同步工具,本质是一把独占钥匙,它保证同一时间只有一个线程进入临界区执行,从而避免多线程同时访问共享资源出现竞态条件。

2.相关函数

(1)初始化互斥锁
  • 静态初始化(推荐,用于全局/静态变量)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 动态初始化(用于局部/堆变量,需要手动销毁)
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

参数:mutex为锁的地址;attr为锁属性(默认传NULL,可设置递归、错误检查等特性)

返回值:成功返回0,失败返回非0错误码

(2)加锁操作:pthread_mutex_lock
// 阻塞加锁:拿不到锁时,线程会被挂起,直到锁可用
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 非阻塞加锁:拿不到锁时,直接返回错误`EBUSY`,不阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
(3)解锁操作:pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex);

注意:只有持有锁的线程能执行解锁操作,解锁后其他线程才有机会获取锁。

(4)销毁互斥锁:pthread_mutex_destroy
int pthread_mutex_destroy(pthread_mutex_t *mutex);

动态初始化的锁,使用完毕必须销毁以释放资源;静态初始化的锁无需手动销毁。

二,条件变量

1.概念

条件变量是一种等待-通知机制,必须和互斥锁结合使用,用于解决线程因条件不满足导致的阻塞场景(比如消费者等待队列不为空),避免忙等待(循环轮询浪费 CPU 资源)。

核心逻辑:

  • 线程持有互斥锁,检查条件是否满足;
  • 若条件不满足,调用pthread_cond_wait()原子地释放互斥锁并进入阻塞状态(原子操作保证不会丢失通知);
  • 其他线程修改条件后,调用通知函数唤醒阻塞线程;
  • 线程被唤醒后,重新获取互斥锁并再次检查条件(处理 “虚假唤醒”)。

关键注意:条件变量存在 “虚假唤醒”(即使无通知,线程也可能被意外唤醒),因此pthread_cond_wait()必须放在while循环中,而不是if语句中。

2.相关函数

(1)初始化条件变量
  • 静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 动态初始化:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
(2)等待条件变量(阻塞)

线程阻塞在条件变量cond上,等待被唤醒;同时,该函数会自动释放传入的互斥锁mutex,当线程被唤醒时,又会自动重新获取该互斥锁;保证了线程在等待条件过程中互斥锁的正确管道,避免死锁等问题;

// 无限等待:直到收到通知,原子释放锁并阻塞,唤醒后重新获取锁
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
(3)唤醒条件变量

唤醒等待在指定条件变量(cond)上的线程

// 唤醒一个等待该条件变量的线程(一对一通知)
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒所有等待该条件变量的线程(广播通知)
int pthread_cond_broadcast(pthread_cond_t *cond);
(4)销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

三,无名信号量

1.概念

无名信号量是一种用于线程同步或进程同步的工具,通过维护一个计数值来控制对共享资源的访问,可限制同时访问资源的线程或进程数量,通过原子的P/V操作实现线程间的互斥与同步。POSIX 信号量分为两类:

  • 有名信号量:用于进程间通信,以文件形式存在;
  • 无名信号量(线程信号量):用于同一进程内的线程同步,本文重点讲解。

核心操作:

  • P 操作(wait/down):申请资源,信号量减 1。若减 1 后结果小于 0,线程阻塞,直到有其他线程释放资源。
  • V 操作(post/up):释放资源,信号量加 1。若有线程因该信号量阻塞,会唤醒其中一个线程。

2.相关函数

(1)初始化无名变量
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

  • sem:信号量的地址;
  • pshared:共享方式,0表示线程间共享(无名信号量用0),非0表示进程间共享;
  • value:信号量的初始值(计数器初始值)。

返回值:成功返回0,失败返回-1并设置errno

(2)P操作(申请资源)

信号量的值减1如果信号量的当前值大于 0,减 1 后函数直接返回(表示“获取资源成功”)如果信号量的当前值等于 0,sem_wait 会阻塞,直到有其他线程/进程调用sem_post函数,使得信号量值大于0后,再执行“减 1”并返回

// 阻塞P操作:拿不到资源时线程阻塞
int sem_wait(sem_t *sem);
(3)V操作(释放资源)

释放信号量(将信号量的值+1)

int sem_post(sem_t *sem);
(4)销毁信号量
int sem_destroy(sem_t *sem);

四,线程的互斥与同步应用场景

工具 应用场景特点 优势 局限性
互斥锁 单一共享资源,同一时刻只允许一个线程访问;主要用于防止数据竞争和不一致性问题 实现简单,易于理解和使用;能有效解决单一资源的互斥访问问题,保障数据的一致性和完整性 如果线程长时间持有锁,可能导致其他线程长时间等待,降低系统并发性能;若使用不当,容易产生死锁问题,比如多个线程循环等待对方持有的锁
条件变量 线程需要在某个条件满足时才能继续执行;配合互斥锁,实现线程间的等待 - 通知机制 避免线程的忙等待,有效节省 CPU 资源,提高程序的运行效率;可以实现更灵活的线程同步逻辑,满足复杂的条件等待和协作需求 必须与互斥锁配合使用,条件的判断和通知的时机需要准确把握,否则可能导致线程无法正确唤醒或错过唤醒,影响程序的正确性
无名信号量 限制对共享资源的并发访问数量;可用于线程或进程的同步和计数控制 可以灵活控制并发访问的资源数量,适用于需要对资源进行定量管理的场景;不仅能实现互斥,还能进行计数同步,功能比互斥锁更丰富 信号量的计数管理需要准确设置和维护,容易出现资源过度使用或浪费的情况;

五,线程池的工作原理

1.线程池的概念

线程池是一种预先创建并管理一组工作线程的技术,用于处理大量短生命周期的并发任务,避免频繁创建 / 销毁线程的开销,同时限制并发线程数量,防止系统资源耗尽。

2.核心组成

模块 作用
任务队列 保存待执行的任务,每个任务包含执行函数和参数
工作线程组 预先创建的一组线程,阻塞在任务队列上,等待任务
同步机制 互斥锁(保护任务队列)、条件变量(通知线程任务到来)
控制接口 任务提交、线程池关闭、扩容 / 缩容(可选)

3.工作流程

  • 初始化:创建指定数量的工作线程,初始化任务队列、互斥锁和条件变量。工作线程启动后,阻塞在条件变量上等待任务。
  • 提交任务:用户提交任务,任务被加入队列,调用pthread_cond_signal()唤醒一个等待的工作线程。
  • 执行任务:被唤醒的线程从队列中取出任务并执行,任务完成后再次阻塞,等待下一个任务。
  • 关闭线程池:设置关闭标志,广播唤醒所有工作线程,线程执行完当前任务后退出,销毁线程池资源。

4.核心优势

  • 减少线程创建 / 销毁的开销,提高程序响应速度;
  • 控制并发线程数量,避免线程过多导致的 CPU 调度开销和内存占用;
  • 任务统一管理,方便扩展任务优先级、超时控制等功能。

六,死锁的产生与解决方法

1.死锁的概念

死锁是指多个线程在执行过程中,因争夺资源而形成互相等待的局面,所有线程都被阻塞,无法继续执行,也无法释放持有的资源,程序陷入永久停滞。

2.死锁的四个必要条件(缺一不可)

死锁的发生必须同时满足以下四个条件,只要破坏任意一个,即可避免死锁:

  1. 互斥条件:资源是互斥访问的,同一时间只能被一个线程持有(如互斥锁)。
  2. 占有且等待:线程已经持有部分资源,又请求其他被占用的资源,且不释放已持有的资源。
  3. 不可剥夺:线程持有的资源不能被其他线程强行剥夺,只能由线程自己释放。
  4. 循环等待:线程之间形成头尾相接的循环等待链,每个线程都在等待下一个线程持有的资源。

3.死锁的解决方法

(1)死锁预防:破坏必要条件
  • 破坏 “占有且等待”:线程一次性申请所有需要的资源,要么全部拿到,要么一个都不拿;
  • 破坏 “不可剥夺”:线程申请不到资源时,主动释放已持有的所有资源,过段时间重新申请;
  • 破坏 “循环等待”:给所有锁定义全局顺序,所有线程按相同顺序加锁(如先加锁号小的,再加锁号大的),避免循环等待。
(2)死锁避免:银行家算法

通过提前判断系统是否处于安全状态,决定是否分配资源,避免进入可能死锁的不安全状态。但该算法需要提前知道每个线程的最大资源需求,在线程编程中较少直接使用。

图示如下:

(3)死锁的检测与解除

定期运行死锁检测算法,判断是否存在死锁,一旦发现,通过以下方式解除:

  • 撤销线程:强制撤销部分死锁线程,释放其持有的资源,打破循环等待;
  • 资源抢占:从死锁线程中强行剥夺部分资源,分配给其他线程,直到死锁解除。
(4)编程中防死锁技巧
  • 避免嵌套锁,能不嵌套就不嵌套;
  • 必须使用多个锁时,统一所有线程的加锁顺序;
  • 使用pthread_mutex_trylock()或限时加锁,避免永久阻塞;
  • 临界区尽量短小,加锁后尽快解锁,减少持有锁的时间。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐