C++条件变量

基本介绍

在多线程编程中有一个条件变量,在头文件中condition_variable,常用在设计线程池的时候使用,该条件变量需要配合mutex才能使用

std::mutex mtx;
std::condition_variable cv;
void print_id (int id) 
{
  std::unique_lock<std::mutex> lck(mtx);// 首先执行到这一句的第一个线程会lck.lock(), 其他线程则会卡在这一句;
  cv.wait(lck);
  /*
  这个函数比较有意思,当执行到这一行的时候,他会lck.unlock(), 然后条件变量接替开始阻塞,这样第二个线程就会
  执行上一句lck初始化,然后所有线程都会走一遍lck.lock()->lck.unlock()->阻塞,然后所有线程都停留在这个位置,
  等待通知,最先得到通知的线程会调用lck.lock()(至于谁先得到通知线程去竞争了),这样就保证下面这句命令操作的原子性
 */
  std::cout << "thread " << id << '\n';
}//类似于只能指针,lck自动unlock
void go() 
{
      std::unique_lock<std::mutex> lck(mtx);
      cv.notify_all();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i) threads[i] = std::thread(print_id,i);
  std::cout << "10 threads ready to race...\n";
  go();//主线程lck.lock()->通知所有wait->                      
  for (auto& th : threads) th.join();
  return 0;
}

上面是个基本的操作,但是常用的方式不是这样,在cplusplus的demo中会设置一个全局标志符,类似于下面这样,但是一开始的时候
我是很好奇为啥要搞一个ready标志符,后来才知道是为了虚假唤醒
这一块还有一个注意事项:notiff_all必须晚于wait才有效果,条件变量的目的是提供同步机制


std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) 
{
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  std::cout << "thread " << id << '\n';
}

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  ready = true;
  cv.notify_all();
}

虚假唤醒

std::mutex mtx;
std::condition_variable cv;
void process
{
    std::unique_lock<std::mutex> lck(mtx);
    while(queue.empty())
    {
        cond.wait();
        /*
        等效于下面代码
        mtx.unlock();
        while(!singnal) ;
        mtx.lock();
        */
    }
    x=queue.pop();
    lck.unlock();//显示解锁互斥量
    do_something(x);
}

举个例子,我们现在有一个生产者-消费者队列和三个线程。

  1. 1号线程从队列中获取了一个元素,此时队列变为空。
  2. 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。
  3. 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。
  4. 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
  5. 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。
  6. 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。

结论

可以看出通过while就有了双保险,如果queue被人偷了,则继续进入循环,没被偷的话就不进入循环往下走了,起到了双保险的效果,这是一种经验套路写法,背住就行了。

参考

[1] http://www.cplusplus.com/reference/condition_variable/condition_variable/
[2] https://stackoverflow.com/questions/53347124/while-loop-for-condition-variable
[3] https://blog.csdn.net/Leeds1993/article/details/52738845?locationNum=2&fps=1

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值