网站建设 锋云科技,网络营销作业,编程网站项目做哪个比较好,买购网官方网站2、条件变量
互斥量防止多个线程同时访问同一共享变量。#xff08;我们称为互斥#xff09;
有一种情况#xff0c;多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务#xff0c;满足了某一个条件#xff0c;线程A才能继续执行。我们称为互斥
有一种情况多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务满足了某一个条件线程A才能继续执行。我们称为同步
条件变量就是来解决同步问题的。 2.1 条件变量产生背景
用一个典型的例子生产-消费说明 static pthread_mutex_t mtx PTHREAD_MUTEX_INITIALIZER;
static int avail 0; /*生产者线程示意代码*/
s pthread_mutex_lock(mtx);
if(s ! 0) do_err(); avail; s pthread_mutex_unlock(mtx);
if(s ! 0) do_err(); /*消费者线程示意代码*/
for(;;){ s pthread_mutex_lock(mtx); if(s ! 0) do_err(); while(avail 0) avail--; /*do something*/ } s pthread_mutex_unlock(mtx); if(s ! 0) do_err(); } 上述代码生产者线程在满足一定条件下将avail。消费者线程不停的循环检查变量avail的状态一旦有可用资源就进行消费处理。虽然可行但循环检查会造成CPU的资源的浪费。条件变量就是为解决这一问题而设计允许一个线程休眠等待直至接获另一线程的通知收到信号去执行某些操作。 2.2 条件变量初始化和销毁
条件变量的数据类型是pthread_cond_t。
静态初始化pthread_cond_t cond PTHREAD_COND_INITIALIZER
动态初始化pthread_cond_init #include pthread.h int pthread_cond_init(pthread_mutex_t *restrict cond, const pthread_condattr_t *restrict attr); 成功0 失败非0 涉及动态初始化的变量就要有销毁 #include pthread.h int pthread_cond_destroy(pthread_cond_t *restrict mutex); 成功0 失败非0
条件变量销毁pthread_cond_destroy 条件变量的初始化和销毁的注意事项类似于互斥量。 2.3 条件变量的通知和等待
2.3.1 函数定义和基本用法
条件变量的主要操作是发送信号和等待。发送信号操作即通知一个或多个处于等待状态的线程某个共享变量的状态已经改变。等待操作是指在接收到一个通知前一直处于阻塞状态。 #include pthread.h int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 成功0 失败非0 看一下手册上的解释
The pthread_cond_wait() functions shall block on a condition variable。
The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).
The pthread_cond_broadcast() function shall unblock all threads currently blocked on the specified condition variable cond. 在解释具体参数前我们先利用这些新函数优化一下上面的“生产-消费”例子看一下基本用法。
static pthread_mutex_t mtx PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond PTHREAD_COND_INITIALIZER;
static int avail 0; /*生产者线程示意代码*/
s pthread_mutex_lock(mtx);
if(s ! 0) do_err(); avail;
s pthread_cond_signal(cond);
if(s ! 0) do_err(); s pthread_mutex_unlock(mtx);
if(s ! 0) do_err(); /*消费者线程示意代码*/
for(;;){ s pthread_mutex_lock(mtx); if(s ! 0) do_err(); while(avail 0){ //注意这里不能用if用while s pthread_cond_wait(cond, mtx); if(s ! 0) do_err(); } while(avail 0) avail--; /*do something*/ } s pthread_mutex_unlock(mtx); if(s ! 0) do_err(); } 2.3.2 pthread_cond_wait函数用法
条件变量与互斥量的天然联系
pthread_cond_wait内部执行的操作如下:
解锁互斥量mutex阻塞调用线程直至另一个线程就条件变量cond发出信号重新锁定mutex
所以条件变量总是要与一个互斥量相关。大家自然也就明白了pthread_cond_wait的第二个参数的意义。pthread_cond_wait必须在pthread_mutex_lock和pthread_mutex_unlock之间。等待相同条件变量的所有线程在调用pthread_cond_wait时必须指定同一互斥量。 pthread_cond_wait中解锁互斥量和陷入对条件变量的等待属于一个原子操作。调用该函数时在调用线程陷入对条件变量的等待之前其他线程不可能获取到该互斥量也不可能就该条件变量发出信号。 pthread_cond_wait使用的通用原则
从上面“生产-消费”的例子中可以看到ptread_cond_wait函数调用放在了一个while循环中而不是用if来判断这是使用条件变量等待条件触发时的一个通用的设计原则。当代码从pthread_cond_wait()返回时并不能确定判断条件的状态应该立即重新检查判断条件在条件不满足的情况下继续休眠等待。 最典型情况时存在多个消费线程等待条件变量通知。如果生产线程调用pthread_cond_broadcast()来唤醒多个等待的消费线程那么只能有一个消费线程能够获取资源进入下一步处理其他消费线程没有竞争到可用资源只能继续wait。
思考一下
如果生产线程调用pthread_cond_signal()来唤醒一个等待的消费线程,上面的情况还会出现吗 在多核处理器下pthread_cond_signal可能会激活多于一个线程阻塞在条件变量上的线程。结果就是当一个线程调用pthread_cond_signal()后多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。这种效应就称为“虚假唤醒”。 所以pthread_cond_signal()手册中的说明是”至少唤醒一个等待线程” 不论是使用while还是if都是引入了一个共享变量来标识是否有可用资源。这里扩展一下说明两个概念边沿触发和水平触发。比如消费者代码如下写法
/*消费者线程示意代码*/
for(;;){ s pthread_mutex_lock(mtx); if(s ! 0) do_err(); s pthread_cond_wait(cond, mtx); if(s ! 0) do_err(); while(avail 0) avail--; /*do something*/ } s pthread_mutex_unlock(mtx); if(s ! 0) do_err(); }
调用pthread_cond_wait时不加任何条件判断直接就等着。会发生什么
因为时多线程生产者线程可能先运行即有可能在调用pthread_cond_wait前生产者线程已经调用了pthread_cond_signal()。pthread_cond_signal就是发个信号唤醒一个在等待的线程。如果没有在等待的就这样了。这种不保留通知事件的情况就是边沿触发。要求关心事件的线程必须提前做好准备。所以上面的写法就有可能丢失事件。
当我们加入一个共享变量作为判断条件时这个变量实际起到了记录事件的作用将事件的有效期延长了。这就是水平触发。编程水平触发后消费者进入wait前先判断是否有事件发生这样就不会丢失事件。 2.3.3 pthread_cond_signal函数用法
这个函数的使用比较简单调用pthread_cond_signal函数时不一定非得使用mutex互斥量。
思考一个问题当使用mutex互斥量时调用pthread_cond_signal()函数发送信号的时机。是放在pthread_mutex_unlock之前还是之后 之前
pthread_mutex_lock xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
缺点在某些系统的实现中会造成等待线程从内核中唤醒由于cond_signal)然后又回到内核空间因为cond_wait返回后会有原子加锁的行为。如线程A调用signal唤醒线程B后还没有来得及调用unlock就切换了。后来线程B先运行了线程B被唤醒准备进行lock操作发现mutex还被占用进入阻塞。这中间可能涉及内核层和用户层切换问题。所以一来一回会有性能损耗。
但是在LinuxThreads里面就不会有这个问题因为在Linux 线程中有两个队列分别是cond_wait队列和mutex_lock队列 cond_signal只是让线程从cond_wait队列移到mutex_lock队列而不用返回到用户空间不会有性能的损耗。
所以在Linux中推荐使用这种模式。 之后
pthread_mutex_lock xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
优点不会出现之前说的那个潜在的性能损耗因为在signal之前就已经释放锁了
缺点有可能在unlock之后signal之前就被调度了。如果unlock和signal之前有个低优先级的线程正在mutex上等待的话那么这个低优先级的线程就会抢占高优先级的线程cond_wait的线程)因为wait的那个线程在等cond没有在等mutex。而这在上面的放中间的模式下是不会出现的。 所以在Linux下最好pthread_cond_signal放中间但从编程规则上说两种都可以。 2.4 条件变量适用场景
类似于Mutex,条件变量也是可以用于同一进程的线程之间也可以用于跨进程。
但不建议使用跨进程因为比较复杂除了设置条件变量的跨进程属性外mutex也要跨进程。
有一篇资料提出慎用进程间条件变量 条件变量是用于多线程/多进程间同步是一种典型的睡眠唤醒用法。P1等待某个事件的发生P2触发事件唤醒P1。 条件变量在初始化时可以通过接口pthread_condattr_setpshared指定该条件变量可用于进程内的线程间同步还是用于进程间同步。 但是在linux的glibc实现中进程间同步却存在着一个缺陷。将导致问题扩散非常严重。原因如下 pthread_cond_t结构体是一个复杂的数据结构包含了等待信息多个进程都可能同时调用等待函数pthread_cond_wait/pthread_cond_timedwait唤醒函数pthread_cond_signal/pthread_cond_broadcast为了防止一个或者多个等待者、唤醒者同时操作pthread_cond_t的成员必须进行互斥所以pthread_cond_t里还有一个锁。接口实现上pthread_cond_wait/pthread_cond_timedwaitpthread_cond_signal/pthread_cond_broadcast都必须先获取锁然后操作数据完毕释放锁。 在多进程上如果某个进程在pthread_cond_xxx的接口里获取了锁以后因某种原因退出了比如某个线程运行异常了。那么悲剧来了锁没有释放。于是其他的进程只要调用到这个条件变量的接口将因为获取不到锁而等待且一直等待下去。一个进程的异常导致所有进程的异常。 令人困惑的是mutex也可用于进程间互斥pthread_mutex_setpshared设置。但是pthread_mutex_t却支持这种场景进程获取到了mutex后复位了没有释放锁OS帮忙释放需要在mutex初始化时设置pthread_mutexattr_setrobust_np。 同样可用于进程间的条件变量为什么没有这个机制