muduo网络库——条件变量(condition variable)

互斥器:加锁原语,用来排他性地访问共享数据;

条件变量:等待某一条件的成立;

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_;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值