线程同步——条件变量+锁
参考博文:https://www.cnblogs.com/zhangxuan/p/6526854.html
简介
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
- 一个线程等待"条件变量的条件成立"而挂起;
- 另一个线程使"条件成立"(给出条件成立信号)。
与互斥锁不同,条件变量是用来等待而不是用来锁住资源的。但它必须配合互斥锁来使用。如果条件为假,一个线程自动阻塞,并释放互斥锁,以使其他线程能够获得锁来改变条件。
pthread_cond_wait()
是原子调用:等待条件变量,解除锁,然后阻塞。
当 pthread_cond_wait()
返回,代表条件变量有信号,同时上锁。
等待条件有两种方式:条件等待 pthread_cond_wait()
和计时等待pthread_cond_timedwait()
,其中计时等待方式如果在给定时刻前条件没有满足,则返回 ETIMEOUT。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()
(或 pthread_cond_timedwait()
,下同)。
激发条件有两种方式:pthread_cond_signal()
激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;
而 pthread_cond_broadcast()
则激活所有等待线程(惊群)。
条件变量的 API
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
使用 cond_attr
指定的属性初始化条件变量 cond
,当 cond_attr
为 NULL
时,使用缺省的属性。LinuxThreads 实现条件变量不支持属性,
因此 cond_attr
参数实际被忽略。pthread_cond_t
类型的变量也可以用 PTHREAD_COND_INITIALIZER
常量进行静态初始化。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
调用 pthread_cond_signal()
后要立刻释放互斥锁,因为 pthread_cond_wait()
的最后一步是要将指定的互斥量重新锁住,如果 pthread_cond_signal()
之后没有释放互斥锁,pthread_cond_wait()
仍然要阻塞。
pthread_cond_signal()
方法使等待条件变量的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能启动。在多处理器上,该函数可能同时唤醒多个线程。
pthread_cond_broadcast()
重启等待该条件变量的所有线程(惊群)。如果没有等待的线程,则什么也不做。
使用
pthread_cond_signal()
不会有“惊群”现象产生,它最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。
如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal()
调用最多发信一次。
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);
pthread_cond_wait()
自动解锁互斥量,并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait()
之前,应用程序必须加锁互斥量。
pthread_cond_wait()
函数返回前,自动重新对互斥量加锁。
pthread_cond_timedwait()
和 pthread_cond_wait()
一样,但它还限定了等待时间。
互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。
wait 方法流程一览:手动对互斥量加锁 → wait 解锁并阻塞本线程 → 条件变量在别的线程中被 signal 触发 → (如果互斥锁被其他线程加锁,阻塞等到解锁)对互斥量加锁 → wait 返回。
int pthread_cond_destroy(pthread_cond_t *cond);
销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy()
之前,必须没有在该条件变量上等待的线程。否则返回 EBUSY
。在 LinuxThreads 的实现中,条件变量不联结资源,除检查有没有等待的线程外,pthread_cond_destroy()
实际上什么也不做。
条件变量为什么一定要配合锁使用
这是为了应对线程 1 在调用 pthread_cond_wait()
但还没有进入 wait cond
的状态的时候,此时线程 2 触发了 cond_singal
的情况。
如果不用锁的话,这个 cond_singal
就丢失了。加了锁的情况是,线程 2 必须等到锁被释放(也就是 pthread_cod_wait()
释放锁并进入 wait_cond 状态 ,此时线程 2 上锁)的时候才能去触发 cond_singal
。
取消点
pthread_cond_wait()
和 pthread_cond_timedwait()
都被实现为取消点。因此,线程被取消之后,在该处等待的线程将立即重新运行,在重新锁定 mutex 后 wait 函数返回,然后执行取消动作。
也就是说,如果 wait 函数被取消,mutex 将依然保持锁定状态,那么线程需要定义退出回调函数来为其解锁。