网站开发的外文文献,wordpress音乐主题公园,有字体设计网站,wordpress高级破解主题【Linux多线程】线程的同步与互斥 目录 【Linux多线程】线程的同步与互斥分离线程Linux线程互斥进程线程间的互斥相关背景概念问题产生的原因#xff1a; 互斥量mutex互斥量的接口互斥量实现原理探究对锁进行封装(C11lockguard锁) 可重入VS线程安全概念常见的线程不安全的情况…【Linux多线程】线程的同步与互斥 目录 【Linux多线程】线程的同步与互斥分离线程Linux线程互斥进程线程间的互斥相关背景概念问题产生的原因 互斥量mutex互斥量的接口互斥量实现原理探究对锁进行封装(C11lockguard锁) 可重入VS线程安全概念常见的线程不安全的情况常见的线程安全的情况常见不可重入的情况常见可重入的情况可重入与线程安全联系可重入与线程安全区别 常见锁概念死锁死锁四个必要条件解决死锁问题避免死锁算法 Linux线程同步条件变量同步概念与竞态条件条件变量函数为什么pthread_cond_wait需要互斥量 条件变量使用规范 作者爱写代码的刚子 时间2024.3.23 前言本篇博客将会介绍线程的同步与互斥、可重入、线程安全、死锁的概念。 Linux线程分为用户级线程和内核的LWP
分离线程 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。 int pthread_detach(pthread_t thread);可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离
pthread_detach(pthread_self());分离后的线程是不能被pthread_join的(错误码22表示非法参数)
分离线程可以由主线程或者线程自己来做设置tid中相关的参数表示该线程不是joinable的
Linux线程互斥
进程线程间的互斥相关背景概念
尝试运行一段抢票代码
#include iostream
#include pthread.h
#include unistd.h
#include cstdlib
#include vector
using namespace std;#define NUM 4class threadData
{
public:threadData(int number){threadname thread- to_string(number);}public:string threadname;};int tickets 3000; // 用多线程模拟一轮抢票void *getTicket(void *args)
{threadData *td static_castthreadData *(args);const char *name td-threadname.c_str();while (true){if(tickets 0){usleep(1000);printf(who%s, get a ticket: %d\n, name, tickets); // ?tickets--;}else{break;}usleep(13); // 我们抢到了票我们会立马抢下一张吗其实多线程还要执行得到票之后的后续动作。usleep模拟}printf(%s ... quit\n, name);return nullptr;
}int main()
{vectorpthread_t tids;vectorthreadData * thread_datas;for (int i 1; i NUM; i){pthread_t tid;threadData *td new threadData(i);thread_datas.push_back(td);pthread_create(tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for (auto thread : tids){pthread_join(thread, nullptr);}for (auto td : thread_datas){delete td;}return 0;
}结果 共享数据导致数据不一致问题与多线程并发访问有关
寄存器不等于寄存器的内容
线程在执行的时候将共享数据加载到CPU寄存器的本质
把数据的内容变成了自己的上下文——以拷贝的方式给自己单独拿了一份
问题产生的原因
分析ticket–
先将tickets读到cpu的寄存器中CPU内部进行–操作将计算结果写回内存
其中每一步都会对应一条汇编操作
ticket-- 1. mov[XXX] eax 2. – 3. mov eax [XXX]
每个线程都要执行这三步同时任何时候线程都有可能被切换同时线程也保存了硬件上下文 线程执行代码时根据这3条依次从上往下执行而在这三条语句的任何地方线程都有可能被切换走。CPU内的寄存器是被所有的执行流共享的但是寄存器里面的数据是属于当前执行流的上下文数据。 线程被切换的时候需要保存上下文 线程被换回的时候需要恢复上下文 我们假设线程A在执行tickets–的任务且tickets为10000。当tickets在寄存器已经计算一次完毕tickets 9999准备将结果写回内存的时候此时发生了线程切换由线程A切至线程B当前线程被拿下来了此时寄存器里的值9999被放在了自己线程A的上下文里头此时线程B也要执行tckets–的任务且是不断循环此tickets–任务读到寄存器计算返回结果当tickets–到50的时候再次–读取寄存器计算-到49准备写回的时候线程B被切走了保存自己的上下文数据注意此时内存中tickets的数据已经是50了。线程A被切回来了需要恢复上下文把原先保存在线程A的值9999重新读回寄存器里执行第三条语句将计算完成的结果写回内存。此时内存中tickets由50变成了9999。我好不容易-到50的数据一瞬间回到解放前了 上述就是典型的数据不一致问题因为线程切换多线程之间并发访问临界资源就会出现数据不一致的问题。上面的不只有–会出现数据不一致的问题在判断tickets 0时也同样会出现数据不一致 我们假设tickets为1此时线程A执行if判断此步骤同样需要在cpu内的寄存器执行的tickets 1 0判断后整准备返回结果时发生线程切换由线程A切至线程B 线程B也要执行if判断把1从内存读到cpu里判断发现tickets 1 0判断后返回结果到内存随后执行tickets–语句这里也要把tickets1读到内存计算计算后把结果0返回至内存。此时线程切换回至线程A线程A继续执行未完成的tickets–语句照样是把tickets 0读到内存计算计算后把结果-1返回至内存。票数只有1张怎么可能出现这种情况呢。归根结底在于此判断发生了数据不一致问题 能够出现数据不一致的问题本质还是线程切换过于频繁。而线程切换的场景如下 时间片到了线程会在内核返回到用户态做检测创造更多的让线程阻塞的场景 临界资源:多线程执行流共享的资源就叫做临界资源临界区:每个线程内部访问临界资源的代码就叫做临界区互斥:任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用原子性(后面讨论如何实现):不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成 互斥量mutex 大部分情况线程使用的数据都是局部变量变量的地址空间在线程栈空间内这种情况变量归属单个线程其他线程无法获得这种变量。但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通过数据的共享完成线程之间的交互。多个线程并发的操作共享变量会带来一些问题。 为什么可能无法获得争取结果? if 语句判断条件为真以后代码可以并发的切换到其他线程usleep 这个模拟漫长业务的过程在这个漫长的业务过程中可能有很多个线程会进入该代码段–ticket 操作本身就不是一个原子操作经过汇编转化为3条语句 – 操作并不是原子操作而是对应三条汇编指令: load :将共享变量ticket从内存加载到寄存器中update : 更新寄存器里面的值执行-1操作store :将新值从寄存器写回共享变量ticket的内存地址 要解决以上问题需要做到三点: 代码必须要有互斥行为:当代码进入临界区执行时不允许其他线程进入该临界区。如果多个线程同时要求执行临界区的代码并且临界区没有线程在执行那么只能允许一个线程进入该临界区。如果线程不在临界区中执行那么该线程不能阻止其他线程进入临界区。 要做到这三点本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。 互斥量的接口
初始化互斥量
pthread_mutex_t是库给我们提供的一种数据类型
初始化互斥量有两种方法: 方法1静态分配 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER//用于定义全局的锁用了这把全局的锁之后就不需要销毁了注意局部的mutex不能采用该宏来初始化
使用全局锁的示例 方法2动态分配 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数: mutex:要初始化的互斥量 attr:NULL销毁互斥量
销毁互斥量需要注意 使用 初始化的互斥量不需要销毁不要销毁一个已经加锁的互斥量已经销毁的互斥量要确保后面不会有线程再尝试加锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);互斥量加锁和解锁 int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//返回值:成功返回0,失败返回错误号调用pthread_mutex_lock时可能会遇到以下情况 互斥量处于未锁状态该函数会将互斥量锁定同时返回成功发起函数调用时其他线程已经锁定互斥量或者存在其他线程同时申请互斥量但没有竞争到互斥量 那么pthread_ lock调用会陷入阻塞(执行流被挂起)等待互斥量解锁。 只需要将临界资源临界区加锁即可加锁和解锁频繁也是会影响程序效率的 加锁的本质用时间来换取安全 加锁的表现线程对于临界区代码串行执行 加锁原则尽量的要保证临界区代码越少越好
#include iostream
#include pthread.h
#include unistd.h
#include cstdlib
#include vector
using namespace std;#define NUM 4class threadData
{
public:threadData(int number,pthread_mutex_t *mutex){threadname thread- to_string(number);lock mutex;}public:string threadname;pthread_mutex_t *lock;};int tickets 3000; // 用多线程模拟一轮抢票void *getTicket(void *args)
{threadData *td static_castthreadData *(args);const char *name td-threadname.c_str();while (true){pthread_mutex_lock(td-lock);//申请锁成功才能往后执行不成功阻塞等待if(tickets 0){usleep(1000);printf(who%s, get a ticket: %d\n, name, tickets); // ?tickets--;pthread_mutex_unlock(td-lock);//这里也要进行解锁}else{pthread_mutex_unlock(td-lock);//注意这里一定要先解锁再跳出循环不然该线程将会一直没有解锁break;}usleep(13); // 我们抢到了票我们会立马抢下一张吗其实多线程还要执行得到票之后的后续动作。usleep模拟后续动作}printf(%s ... quit\n, name);return nullptr;
}int main()
{pthread_mutex_t lock;pthread_mutex_init(lock,nullptr);vectorpthread_t tids;vectorthreadData * thread_datas;for (int i 1; i NUM; i){pthread_t tid;threadData *td new threadData(i,lock);thread_datas.push_back(td);pthread_create(tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for (auto thread : tids){pthread_join(thread, nullptr);}for (auto td : thread_datas){delete td;}pthread_mutex_destroy(lock);return 0;
}线程对锁的竞争能力可能会不同有的线程刚释放锁后又去申请锁了所以我们等线程释放锁后加上usleep 线程释放锁后不加usleep的后果 不加usleep会导致已经抢到锁的线程刚释放锁但由于靠锁近其他线程还没来得及唤醒竞争锁该锁又被它抢走了。
纯互斥环境如果锁分配不够合理(比如一个锁的竞争能力非常强)容易导致其他线程的饥饿问题
但不是说只要有互斥就必有饥饿适合纯互斥的场景就用互斥
所以我们可以定两条规则
等待资源就绪的线程必须排队刚释放锁的线程不能立马重新申请锁必须排队到队列的尾部
所以同步让所有的线程按照一定的顺序获取锁和资源叫做同步 注意锁本身也是一种共享资源申请锁和释放锁本身就被设计成为了原子性操作 在临界区中线程可以被切换吗可以在线程被切出去的时候是持有锁被切走的不是持有锁的线程就不能进入临界区访问临界资源加锁能保证我当前线程在访问临界区期间对于其他线程来讲是原子的只有两种状态持有锁和非持有锁其他线程不关心持有锁线程里面的执行过程 互斥量实现原理探究
【问题】线程加锁和解锁具有原子性原子性是如何实现的呢 经过上面的例子大家已经意识到单纯的 i 或者 i 都不是原子的有可能会有数据一致性问题为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一 个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 锁的原理
ticket——不是原子的会变成3条汇编语句。原子一条汇编语句就是原子的
加锁的汇编语句: 以加锁示例这是由多态汇编语句执行的上述%al是寄存器mutex就是内存中的一个变量。每个线程申请锁时都要执行上述语句执行步骤如下
movb $0%al先将al寄存器中的值清0。该动作可以被多个线程同时执行因为每个线程都有自己的一组寄存器上下文信息执行该动作本质上是将自己的al寄存器清0。注意凡是在寄存器中的数据全部都是线程的内部上下文多个线程看起来同时在访问寄存器但是互不影响。xchgb %almutex然后用此一条指令交换al寄存器和内存中mutex的值xchgb是体系结构提供的交换指令该指令可以完成寄存器和内存单元之间数据的交换。最后判断al寄存器中的值是否大于0。若大于0则申请锁成功此时就可以进入临界区访问对应的临界资源否则申请锁失败需要被挂起等待直到锁被释放后再次竞争申请锁。
示例假设内存中有一个变量mutex为1cpu内部有%al寄存器我们有threadA和threadB俩线程
现在线程A要开始加锁执行上述语句。首先movb $0%al线程A把0读进al寄存器清0寄存器然后执行第二条语句xchgb %almutex将al寄存器中的值与内存中mutex的值进行交换。 交换完成后寄存器al的值为1内存中mutex的值为0。此时这个过程就是加锁 当线程A争议执行第三条语句if判断时发生了线程切换切至线程B但是线程A要把自己的上下文1带走。线程B也要执行加锁动作同样是第一条语句把0加载到寄存器清0寄存器。 随后线程B执行第二条语句交换动作可是mutex的数据先前已经被线程A交换至寄存器然后保存到线程A的上下文了现在的mutex为0而线程B执行交换动作拿寄存器al的0去换内存中mutex的0。 随后线程B执行第三条语句if判断可是我现在寄存器的值为0判断失败线程B挂起等待。此时线程B就叫做申请锁失败。
即使我线程A在执行第一条语句把寄存器清0后就发生了线程切换切至线程B线程A保存上下文数据0此时线程B执行第一条语句把0写进寄存器随后线程B执行第二条语句xchgb交换 当线程B好不容易拿到1将要进行if判断时又发生了线程切换切至线程A线程B保留自己的上下文数据1线程A恢复上下文数据0到寄存器。随后线程A继续执行第二条xchgb交换语句可是现在mutex为0啊交换后寄存器的值依旧为0。 此时线程A执行第三条语句if判断失败只能被挂起等待线程A只能把自己的上下文数据保存重新切换至线程B也就是说我线程B只要不运行你们其它所有线程都无法申请成功。线程B恢复上下文数据1到内存然后执行第三条语句if成功返回结果 注意上述xchgb就是申请锁的过程。申请锁是将数据从内存交换到寄存器本质就是将数据从共享内存变成线程私有。 mutex就是内存里的全局变量被所有线程共享但是一旦用一条汇编语句原子将内存的mutex值交换到寄存器寄存器内部是哪个线程使用那么此mutex就是哪个线程的上下文数据那么就意味着交换成功后其它任何一个线程都不可能再申请锁成功了因为mutex已经独属于某线程私有了(交换成功后变成了线程的上下文而线程的上下文是私有的即把一个共享资源临界资源变成了私有资源)。 这个mutex 1就如同令牌一般哪个线程先交换拿到1那么哪个线程就能申请锁成功所以加锁是原子的 当线程释放锁时需要执行以下步骤 将内存中的mutex置回1。使得下一个申请锁的线程在执行交换指令后能够得到1形象地说就是“将锁的钥匙放回去”。唤醒等待Mutex的线程。唤醒这些因为申请锁失败而被挂起的线程让它们继续竞争申请锁。 总结 在申请锁时本质上就是哪一个线程先执行了交换指令那么该线程就申请锁成功因为此时该线程的al寄存器中的值就是1了。而交换指令就只是一条汇编指令一个线程要么执行了交换指令要么没有执行交换指令所以申请锁的过程是原子的。 在线程释放锁时没有将当前线程al寄存器中的值清0这不会造成影响因为每次线程在申请锁时都会先将自己al寄存器中的值清0再执行交换指令。 CPU内的寄存器不是被所有的线程共享的每个线程都有自己的一组寄存器但内存中的数据是各个线程共享的。申请锁实际就是把内存中的mutex通过交换指令原子性的交换到自己的al寄存器中。 把一个共享的锁让一个线程以一条汇编的方式交换到自己的上下文中把锁变成了线程私有 解锁的汇编语句 当然不一定只能同一个线程来申请锁和解锁也可以一个线程加锁一个线程来解锁来实现两个线程的同步或者也可以避免死锁问题。
对锁进行封装(C11lockguard锁) #pragma once
#include iostream
class Mutex
{
public:Mutex(pthread_mutex_t *lock):lock_(lock){}void Lock(){pthread_mutex_lock(lock_);}void Unlock(){pthread_mutex_unlock(lock_);}~Mutex(){}
private:pthread_mutex_t *lock_;
};
class LockGuard
{
public:LockGuard(pthread_mutex_t *lock):mutex_(lock){mutex_.Lock();}~LockGuard(){mutex_.Unlock();}
private:Mutex mutex_;
};RAII思想的锁
可重入VS线程安全
概念 线程安全:多个线程并发同一段代码时不会出现不同的结果。常见对全局变量或者静态变量进行操作并且没有锁保护的情况下会出现该问题。重入:同一个函数被不同的执行流调用当前一个流程还没有执行完就有其他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数可能会出现线程安全问题。 注意线程安全和重入是两个概念线程安全与不安全描述的是线程并发的问题重入是描述函数的特点没有褒贬之分只是函数的特征。我们现在接触的大部分函数都是不可被重入的printf、scanf、文件操作、stl库。
常见的线程不安全的情况 不保护共享变量的函数函数状态随着被调用状态发生变化的函数如使用static局部变量返回指向静态变量指针的函数当一个线程正在访问静态变量时另一个线程也可能在同时进行访问或修改这样就有可能造成数据的不一致性或者未定义行为竞态条件。调用线程不安全函数的函数会发生异常崩溃的函数 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限而没有写入的权限一般来说这些线程是安全的类或者接口对于线程来说都是原子操作多个线程之间的切换不会导致该接口的执行结果存在二义性 常见不可重入的情况 调用了malloc/free函数因为malloc函数是用全局链表来管理堆的调用了标准I/O库函数标准I/O库的很多实现都以不可重入的方式使用全局数据结构可重入函数体内使用了静态的数据结构 常见可重入的情况 不使用全局变量或静态变量不使用用malloc或者new开辟出的空间不调用不可重入函数不返回静态或全局数据所有数据都有函数的调用者提供使用本地数据或者通过制作全局数据的本地拷贝来保护全局数据 可重入与线程安全联系 函数是可重入的那就是线程安全的函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别 可重入函数是线程安全函数的一种线程安全不一定是可重入的而可重入函数则一定是线程安全的。如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数若锁还未释放则会产生死锁因此是不可重入的。 常见锁概念
死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 死锁四个必要条件 互斥条件:一个资源每次只能被一个执行流使用 最重要的前提请求与保持条件:一个执行流因请求资源而阻塞时对已获得的资源保持不放原则不剥夺条件:一个执行流已获得的资源在末使用完之前不能强行剥夺 原则循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系重要条件 一旦产生了死锁以上四个条件必须同时满足
申请锁失败线程就会阻塞等待产生死锁多次申请同一个锁会产生死锁
解决死锁问题
理念破坏4个必要条件只需要一个不满足就可以的
方法
请求与保持条件与不剥夺条件是可以通过函数来解决的破坏循环等待条件申请锁时按照一定的顺序一个线程申请锁一、锁二另一个线程申请锁一、锁二不能一个线程申请锁一、锁二另一个线程申请锁二、锁一 破坏死锁的四个必要条件加锁顺序一致避免锁未释放的场景资源一次性分配 避免死锁算法 死锁检测算法(了解)银行家算法(了解) Linux线程同步
如何让线程实现排队条件变量
条件变量 当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。例如一个线程访问队列时发现队列为空它只能等待只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。 条件变量要提供通知机制要提供一个队列能让线程在队列里排队申请锁失败了就去排队条件变量需要被库管理
条件变量主要包括两个动作
一个线程等待条件变量的条件成立而被挂起。另一个线程使条件成立后唤醒等待的线程。
条件变量通常需要配合互斥锁一起使用。
我们学到的大部分概念只要能创建多个的都需要被管理起来。
同步概念与竞态条件 同步:在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题叫做同步强调顺序性竞态条件:因为时序问题而导致程序异常我们称之为竞态条件。在线程场景下这种问题也不难理解 有可能有人会想既然线程都排队了那同步问题为什么要带锁呢其实这个问题是矛盾的线程排队并不是自愿的而是锁强迫的有互斥的前提访问资源失败了才回去排队。
条件变量函数
有关条件变量这篇博客也有介绍
【C11】线程库
初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量 attr:NULL销毁
int pthread_cond_destroy(pthread_cond_t *cond)定义全局的条件变量
pthread_cond_t cond PTHREAD_COND_INITIALIZER;等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数: cond:要在这个条件变量上等待 mutex:互斥量唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个线程案例
定义了一把锁和条件变量 【问题】为什么有时候线程向显示器输出的时候会出现混乱
显示器也是文件是临界资源会被多线程进行竞争。
对打印函数进行加锁 我们发现一个线程的竞争能力太大了所以我们再次修改代码 mutex_让线程进行等待前需要将锁进行释放其他线程就会因为申请不了锁而产生死锁问题。所以pthread_cond_wait让线程等待的时候会自动释放锁 如何唤醒线程 使用pthread_cond_broadcast函数进行测试 【问题】我们怎么知道要让一个线程去休眠
一定是临界资源不就绪临界资源也是有状态的
线程遇到临界资源不就绪走了就叫互斥不走而是排队则叫同步
【问题】怎么知道临界资源是否就绪判断是访问临界资源吗
程序猿自己判断临界资源是否就绪判断也是访问临界资源所以判断必须在加锁之后
所以pthread_cond_wait休眠函数必须在加锁和解锁之间因为需要判断临界资源是否就绪然后是否进行线程休眠与之前pthread_cond_wait休眠之前需要释放锁相呼应
为什么pthread_cond_wait需要互斥量 条件等待是线程间同步的一种手段如果只有一个线程条件不满足一直等下去都不会满足所以必须要有一个线程通过某些操作改变共享变量使原先不满足的条件变得满足并且友好的通知等待在条件变量上的线程。条件不会无缘无故的突然变得满足了必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。 按照上面的说法我们设计出如下的代码:先上锁发现条件不满足解锁然后等待在条件变量上不就 行了如下代码: // 错误的设计
pthread_mutex_lock(mutex);
while (condition_is_false) {pthread_mutex_unlock(mutex); //解锁之后等待之前条件可能已经满足信号已经发出但是该信号可能被错过 pthread_cond_wait(cond);pthread_mutex_lock(mutex);
}
pthread_mutex_unlock(mutex);由于解锁和等待不是原子操作。调用解锁之后 pthread_cond_wait 之前如果已经有其他线程获取到 互斥量摒弃条件满足发送了信号那么 pthread_cond_wait 将错过这个信号可能会导致线程永远 阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后 会去看条件量等于0不?等于就把互斥量变成1直到cond_ wait返回把条件量改成1把互斥量恢复 成原样。 条件变量使用规范 等待条件代码 pthread_mutex_lock(mutex);
while (条件为假)pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(mutex);给条件发送信号代码 pthread_mutex_lock(mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(mutex);【附】
小细节不能对i取地址因为可能会对后续循环中的i产生影响,要保证i独立