互斥器:加锁原语,用来排他性地访问共享数据;
条件变量:等待某一条件的成立;
1. 概念
一个或多个线程等待某个布尔表达式为真,即等待别的线程“唤醒”它。条件变量的学名叫管程(monitor)。Java Object内置的wait()、notify()、notifyAll()是条件变量。
2. 使用
对于wait端:
1)必须与mutex一起使用,该布尔表达式的读写需受此mutex保护;
2)在mutex已上锁的时候才能调用wait();
3)把判断布尔条件和wait()放到while循环中;
muduo库中的源码如下:
muduo::MutexLock mutex;
muduo::Condition cond(mutex);
std::deque<int> queue;
int dequeue()
{
MutexLockGuard lock(mutex);
while(queue.empty()) //必须用循环;必须在判断之后再wait()
{
cond.wait(); //这一步原子地unlock mutex并进入等待,不会与enqueue死锁
//wait()执行完毕时会自动重新加锁
}
assert(!queue.empty());
int top = queue.front();
queue.pop_front();
return top;
}
上面的代码必须用while循环来等待条件变量,不能用if语句,原因是虚假唤醒(spurious wakeups)。
3. 虚假唤醒
https://en.wikipedia.org/wiki/Spurious_wakeup中的解释如下:
在条件变量发出信号和等待线程最终运行之间,另一个线程运行并更改了条件。线程之间存在竞争条件,在条件变量上唤醒的线程首先运行,其他线程被唤醒之后发现条件已经改变,无法继续执行。
在许多系统上,尤其是多处理器系统上,虚假唤醒的问题会加剧产生。因为如果有多个线程在发出信号时等待条件变量,系统可能会决定将它们全部唤醒,将signal()
作为broadcast()
唤醒所有线程的条件,从而打破信号和唤醒之间的 1:1 关系。如果有 10 个线程在等待,则只有 1 个线程获胜,其他 9 个线程将经历虚假唤醒。
知乎上的一种解释:
pthread 的条件变量等待 pthread_cond_wait
是使用阻塞的系统调用实现的(比如 Linux 上的 futex
),这些阻塞的系统调用在进程被信号中断后,通常会中止阻塞、直接返回 EINTR 错误。
同样是阻塞系统调用,你从 read
拿到 EINTR 错误后可以直接决定重试,因为这通常不影响它本身的语义。而条件变量等待则不能,因为本线程拿到 EINTR 错误和重新调用 futex
等待之间,可能别的线程已经通过 pthread_cond_signal
或者 pthread_cond_broadcast
发过通知了。
针对上面的例子个人理解:
多个线程在同时等待queue不为空的条件发生。当有信号signalA通知queue不为空时,操作系统同时唤醒所有线程,如线程A竞争成功,执行下面的pop操作,使得queue再次为空。但其他线程此时也被唤醒,又由于线程A的操作导致queue再次为空,其他线程无法执行对应的操作,即被虚假唤醒。
4. CountDownLatch
条件变量时非常底层的同步原语,很少直接使用,一般都是用它来实现高层的同步措施,如CountDownLatch或BlockingQueue。
4.1 用途
- 主线程发起多个子线程,等这些子线程各自都完成一定的任务后,主线程才继续执行。通常用于主线程等待多个子线程完成初始化;
- 主线程发起多个子线程,子线程都等待主线程,主线程完成其它一些任务之后通知所有子线程开始执行。通常用于多个子线程等待主线程发出"起跑"命令;
4.2 实现
class CountDownLatch : noncopyable
{
public:
explicit CountDownLatch(int count);
void wait();
void countDown();
int getCount() const;
private:
mutable MutexLock mutex_;
Condition condition_ GUARDED_BY(mutex_);
int count_ GUARDED_BY(mutex_);
};
CountDownLatch::CountDownLatch(int count)
: mutex_(),
condition_(mutex_),
count_(count)
{
}
void CountDownLatch::wait()
{
MutexLockGuard lock(mutex_);
while (count_ > 0)
{
condition_.wait();
}
}
void CountDownLatch::countDown()
{
MutexLockGuard lock(mutex_);
--count_;
if (count_ == 0)
{
condition_.notifyAll(); //通知所有子线程
}
}
int CountDownLatch::getCount() const
{
MutexLockGuard lock(mutex_);
return count_;
}