线程同步之条件变量
互斥量防止多个线程同时访问同一共享变量。
条件变量允许一个线程就某个条件(共享变量)的变化状态通知其他线程,并让其他线程等待(阻塞于)该通知。
条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。
条件变量
条件一般是多线程共享的全局变量,由互斥量保护用于多线程同步,其中一个线程检测条件发生变化然后通知其他线程。
条件变量的主要操作是发送信号和等待。
发送信号操作即通知一个或多个处于等待状态的线程,条件的状态已经发生改变。
等待操作是指受到一个通知前一直处于阻塞状态。
初始化条件变量
条件变量 | 描述 |
---|---|
数据类型 | pthread_cond_t |
静态分配 | pthread_cond_t cond=PTHREAD_COND_INITIALIZER |
动态分配 | pthread_cond_init() 和pthread_cond_destroy() |
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_destroy(pthread_cond_t *cond);
// RETURN VALUE
// All condition variable functions return 0 on success and a non-zero error code on error
pthread_cond_init()
接口参数cond_attr
为NULL
时,使用默认参数。
通知条件变量
函数pthread_cond_signal()
与pthread_cond_broadcast
可对由参数cond
指定的条件变量而发送信号。
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
// RETURN VALUE
// All condition variable functions return 0 on success
// and a non-zero error code on error
pthread_cond_signal()
函数唤醒至少一条由于等待cond
指定的信号而处于阻塞的线程,pthread_cond_broadcast()
函数会唤醒所有阻塞的线程。
只有当仅需唤醒一条等待线程来处理条件(共享变量)的状态变化时,才应使用pthread_cond_signal()
,因为这规避了pthread_cond_broadcast()
同时唤醒多条等待线程进行竞争的时间消耗。
常见使用pthread_cond_signal()
函数的情况是,所有等待线程都在执行相同的任务。
pthread_cond_signal()
函数既可以位于pthread_mutex_lock()
与pthread_mutex_unlock()
之间,也可以在其后,对于线程同步没有影响,但是效率上并不一致。
之间
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond, &mutex);
pthread_mutex_unlock(&mutex);
//...
调用pthread_cond_wait()
的线程可能会在互斥量仍处于锁住状态醒来(内核空间),然后没有获得锁(用户空间),再次进入休眠状态(重新返回内核空间)。这可能会多出两个上下文切换的性能消耗。
但是在Linux Threads或NPTL的线程实现里面,只会出现等待队列转移。
因为在Linux线程中,有两个队列,分别是cond_wait队列和mutex_lock队列,cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
之后
pthread_mutex_lock(&mutex);
// ...
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond, &mutex);
如果pthread_mutex_unlock()
与pthread_cond_signal()
之间,有低优先级的线程正阻塞在mutex
互斥量上,那么该低优先级的线程就会抢占高优先级的线程(阻塞在cond
上的线程),而这在上面的放中间的模式下是不会出现的。
等待条件变量
函数pthread_cond_wait()
与pthread_cond_timedwait()
用于阻塞线程,直到收到条件变量cond
的通知或者到达指定的等待时间。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
// RETURN VALUE
// All condition variable functions return 0 on success
// and a non-zero error code on error
abstime
用于指定等待的绝对时间,即当前时间+等待时间。mutex
参数为保护条件并且是锁住的互斥量。
这两个函数具体要执行以下步骤:
- 解锁互斥量
mutex
。 - 阻塞调用线程,直到其他线程就该条件变量
cond
发出信号。 - 重新锁定
mutex
。
条件变量的使用方法一般为:
- 锁住互斥量
mutex
。 - 检查条件。
- 条件不满足,则调用
pthread_cond_wait()
或pthread_cond_timedwait()
阻塞线程,等待条件满足。 - 收到信号,再次检查条件。条件满足则执行下一步任务,条件不满足返回步骤3。
条件变量使用范例:
if(pthread_mutex_lock(&mutex)!=0)
exit(-1);
while(/*条件不满足*/)
pthread_cond_wait(&cond, &mutex);
// other task
if(pthread_mutex_unlock(&mutex)!=0)
exit(-1);
检查条件时锁住互斥量的原因在于条件属于共享变量,而检查条件和线程等待条件不属于原子操作。
必须在while
循环中检查条件的原因在于pthread_cond_wait()
函数返回并不能代表条件满足,所以应该立即重新检查条件,不满足重新休眠等待。
从pthread_cond_wait()
返回时不能对条件做任何假设的原因有:
- 其他线程可能会率先醒来改变条件。即使就条件变量发出通知的线程将条件设置为预期状态,也不能阻止这种情况的发生。
- 设计时应该用条件变量表征可能性而非确定性。
- 可能会出现虚假唤醒的情况。没有其他线程就该条件变量发出信号,但是等待线程依然有可能醒来。
实例
在下面的示例中,条件是队列的状态,用互斥量保护条件,在while
循环中判断体条件。
将消息放到工作队列时,需要占用互斥量,给线程发送信号时无需占用互斥量。
#include <pthread.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void
process_msg(void)
{
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL)
// 这儿加锁的原因在于保护条件,并保证条件检查和将进程放到等待条件队列属于原子操作。
// 否则条件检查完有可能出现其他线程改变条件,但当前线程还是会阻塞。
pthread_cond_wait(&qready, &qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */
}
}
void
enqueue_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
// 这儿发信号放在解锁之后,可能会出现阻塞在qlock的其他线程先占有锁,并改变条件。
// 但是由于是在while中检查条件,因此并不影响功能。
// Linux中采用的是NPTL线程模型,因此推荐pthread_cond_signal在互斥量里面调用,
// 不会产生解锁再阻塞的性能问题。
pthread_cond_signal(&qready);
}
- 原文作者:生如夏花
- 原文链接:https://blduan.top/post/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/apue/%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E4%B9%8B%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。