Linux线程同步
目录1 基本概念2 条件变量3 条件变量的核心操作3.1 条件变量初始化3.1.1 静态初始化编译时初始化3.1.2 动态初始化运行时初始化3.1 ptread_cond_wait3.1.1 pthread_cond_wait 的工作原理3.1.2 常见问题3.2 pthread_cond_signal3.2.1 基本工作原理3.2.4 常见错误用法3.3 pthread_cond_broadcast1 基本概念线程同步是指通过特定的机制控制多个线程按照一定的顺序或规则访问共享资源确保线程安全。为什么需要线程同步数据一致性防止多个线程同时修改共享数据导致数据损坏避免竞态条件确保操作的原子性有序执行控制线程执行的先后顺序从而有效的防止线程饥饿2 条件变量条件变量Condition Variable是一种线程同步机制用于让线程在某个条件不满足时进入等待状态并在条件满足时被唤醒继续执行。它通常与互斥锁Mutex配合使用以实现更复杂的线程同步逻辑。为什么需要条件变量在多线程编程中有时线程需要等待某个条件成立才能继续执行。如果仅使用互斥锁线程可能需要不断轮询检查条件忙等待这会浪费CPU资源。条件变量提供了一种高效等待机制让线程在条件不满足时进入休眠状态直到其他线程通知它条件可能已满足。3 条件变量的核心操作条件变量通常提供三个基本操作wait(lock)线程调用wait时会释放锁并进入等待状态直到被唤醒。被唤醒后它会重新获取锁然后继续执行。必须配合互斥锁使用以防止竞态条件。signal()/notify_one()唤醒一个正在等待该条件变量的线程如果有多个线程在等待只唤醒其中一个。broadcast()/notify_all()唤醒所有正在等待该条件变量的线程。3.1 条件变量初始化3.1.1 静态初始化编译时初始化pthread_cond_t cond PTHREAD_COND_INITIALIZER;使用宏定义初始化总是使用默认属性线程安全不需要显式销毁3.1.2 动态初始化运行时初始化#include pthread.h int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);cond: 指向要初始化的条件变量的指针attr: 指向条件变量属性对象的指针通常设为NULL使用默认属性返回值:成功返回0失败返回错误码如EAGAIN表示资源不足ENOMEM表示内存不足pthread_cond_t cond; // 初始化 pthread_cond_init(cond, NULL); // 销毁 pthread_cond_destory(cond);更灵活可以指定属性需要配合pthread_cond_destroy释放资源可以在运行时决定初始化时机3.1 ptread_cond_waitpthread_cond_wait是 POSIX 线程pthread库中用于条件变量等待的核心函数它允许线程在某个条件不满足时挂起阻塞并在条件可能满足时被唤醒。它通常与互斥锁pthread_mutex_t配合使用以实现线程间的同步。#include pthread.h int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);cond: 指向条件变量的指针pthread_cond_t。mutex: 指向互斥锁的指针pthread_mutex_t调用pthread_cond_wait前必须已加锁。返回值成功返回0。失败返回错误码如EINVAL表示参数无效3.1.1 pthread_cond_wait 的工作原理(1) 调用时发生了什么原子地释放mutex传递进来的锁资源并让当前线程进入等待状态阻塞。线程被移入条件变量的等待队列直到被pthread_cond_signal或pthread_cond_broadcast唤醒。被唤醒后需要重新获取争夺锁资源mutex可能阻塞直到锁可用直到争夺到锁资源然后继续执行。(2) 为什么需要mutex防止竞态条件检查条件和进入等待必须是原子操作否则可能出现线程A检查条件如queue.empty()发现true准备进入wait。线程B在A进入wait前修改了条件如queue.push()并发出signal。线程A进入wait但信号已经丢失导致永久等待。pthread_cond_wait内部会先解锁mutex再进入等待确保signal不会丢失。3.1.2 常见问题(1) 虚假唤醒Spurious Wakeup线程可能在没有收到signal的情况下被唤醒由于系统优化或信号中断。解决方案始终在while循环中检查条件while (!condition) { pthread_cond_wait(cond, mutex); }(2) 死锁风险忘记解锁如果pthread_cond_wait后忘记解锁其他线程无法获取锁。signal丢失如果signal在wait前发出可能导致线程永久等待。解决方案确保signal发生在wait之后或使用pthread_cond_broadcast。(3)pthread_cond_wait和pthread_mutex_unlock的顺序错误用法pthread_mutex_unlock(mutex); pthread_cond_wait(cond, mutex); // 未持有锁时调用行为未定义正确用法pthread_mutex_lock(mutex); pthread_cond_wait(cond, mutex); // 内部会先解锁再等待 pthread_mutex_unlock(mutex);3.2 pthread_cond_signalpthread_cond_signal是 POSIX 线程pthread库中用于唤醒等待在条件变量上的线程的关键函数。它通常与pthread_cond_wait配合使用实现线程间的同步通信。#include pthread.h int pthread_cond_signal(pthread_cond_t *cond);cond: 指向要发送信号的条件变量的指针返回值:成功返回0失败返回错误码如EINVAL表示无效的条件变量3.2.1 基本工作原理pthread_cond_signal的主要作用是唤醒至少一个正在pthread_cond_wait的线程如果有多个线程在等待具体唤醒哪个取决于实现如果没有线程在等待这个信号会被丢弃不会累积关键特性非阻塞调用pthread_cond_signal不会阻塞当前线程不保证立即执行被唤醒的线程需要重新获取互斥锁后才能继续执行无记忆性如果调用signal时没有线程在等待信号不会保存供后续使用关键注意事项调用时通常应持有锁最佳实践是在持有互斥锁时调用signal这样可以避免信号丢失在检查和等待之间的竞争条件信号可能丢失如果在没有线程等待时调用signal信号会被丢弃这通常不是问题因为正确的模式应该使用条件变量谓词检查虚假唤醒仍然可能发生即使使用signal被唤醒的线程仍应该检查条件使用while而不是if性能考虑signal比broadcast更轻量在只需要唤醒一个线程的情况下优先使用signal底层实现机制pthread_cond_signal的具体实现依赖于操作系统但通常涉及检查条件变量的等待队列从队列中移出一个线程FIFO或优先级顺序取决于实现将该线程标记为可运行状态在Linux中这通常通过futex快速用户空间互斥锁机制实现避免了不必要的内核切换。3.2.4 常见错误用法1错误1不加锁调用// 错误可能导致竞争条件 pthread_cond_signal(cond);应该pthread_mutex_lock(mutex); pthread_cond_signal(cond); pthread_mutex_unlock(mutex);2错误2使用if而不是while检查条件// 错误可能因虚假唤醒导致问题 if (!condition) { pthread_cond_wait(cond, mutex); }应该:while (!condition) { pthread_cond_wait(cond, mutex); }3.3 pthread_cond_broadcast特性pthread_cond_signalpthread_cond_broadcast唤醒线程数至少一个所有等待线程适用场景单消费者/任意一个线程处理即可多消费者/所有线程都需要处理性能影响较低只唤醒一个较高唤醒所有使用频率更常用特定场景使用