关于C++11标准中的condition_variable对象使用

可能C++11标准似乎也准备在过时了。
最近研究了一下C++11的并行化,其实thread包装的还是不错的,锁的功能也比较丰富,也许是我没什么多线程的经验才会这么认为吧。

但是C++11官方对条件变量的实现机制,说的很不明确,让人看得云里雾里,同时例子也很诡异。
天下文章一大抄,有很多网上的介绍也没有写出自己的想法来,都是翻译的官方的说法。
这个文章也不例外,仅仅做一下自己学习的思路记录和想法。

具体见:http://www.cplusplus.com/reference/condition_variable/condition_variable/

例子如下

// condition_variable example
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

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();
}

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();                       // go!

  for (auto& th : threads) th.join();

  return 0;
}

说明为

condition variable is an object able to block the calling thread until notified to resume.
It uses a unique_lock (over a mutex) to lock the thread when one of its wait functions is called. The thread remains blocked until woken up by another thread that calls a notification function on the same condition_variable object.
Objects of type condition_variable always use unique_lock<mutex> to wait: for an alternative that works with any kind of lockable type, see condition_variable_any

条件变量对象是用来阻止线程而调用的,直到通知恢复它(这个通知是可以从其他线程发出)。
当它调用wait函数的时候,用一个unique_lock对象来锁住线程,线程将会一直被阻塞,直到其他线程调用同一个条件变量对象的notification函数,来解开本线程的阻塞状态。
条件变量对象总是用一个unique_lock对象来等待,对于调用其他锁类型的方法,可以参见condition_variable_any。

个人理解

这段话大概讲清楚了condition_variable的作用,上面例程也体现了这一点,但是代码中有一点就看不懂了,不是说unique_lock带互斥量的构造函数会自动调用互斥量的lock方法么,为啥它main一上来就开了10个线程,最后还想表达一下10个线程互相比赛的意思??
既然一个线程获得了锁,其他线程进行不下去才对啊,那go函数还能运行下去?这不是死锁了??

最后找到了这个比较好的资料,详细介绍了C++11多线程中实现的进一步浅层的机制
https://baptiste-wicht.com/posts/2012/04/c11-concurrency-tutorial-advanced-locking-and-condition-variables.html
A condition variable manages a list of threads waiting until another thread notify them. Each thread that wants to wait on the condition variable has to acquire a lock first. The lock is then released when the thread starts to wait on the condition and the lock is acquired again when the thread is awakened.
条件变量管理等待另一个线程通知它们的线程列表。想要在条件变量上等待的每个线程必须首先获取锁。当线程进入条件变量wait函数的时候,释放锁,并且当线程被唤醒时再次获取锁定。

最后代码例子可以理解为

首先建立了10个线程,然后每个线程都先声明自己有这个锁的权力,也就是unique_lock构造函数,之后调用wait方法,释放了对锁的所有权,也就是锁没了,等待着获得锁,整个线程将阻塞。
之后进入go函数,先获得锁,然后标志位转换,最后调用条件变量的notify_all方法,函数执行完,自动释放锁,像大人逗孩子一样把锁抛了出去,留下的是10个互相争抢锁的孩子。。所以才是可以理解为,10个线程互相抢锁的过程。也是让所有线程同步的一种方法。

遗留的问题:

那么,那个ready到底起了什么作用呢,或者这个while起了什么作用呢?
我们现在把ready的影响去掉,代码改为:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex mtx;	//全局互斥锁
std::condition_variable cv;//全局条件变量

void do_print_id(int id)
{
	std::unique_lock<std::mutex> lck(mtx);		//获得锁
	cv.wait(lck);
	std::cout << "thread" << id << "\n";
}

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

int main()
{
	std::thread threads[10];

	for (int i = 0; i < 10; i++)
	{
		threads[i] = std::thread(do_print_id, i);
	}
	std::cout << "10 threads ready to race...\n";
	go();
	for (auto& th : threads)
		th.join();

	getchar();
	return 0;
}

线程执行的效果是一样的,没有错误。

我们可以在c++11的参考中找到答案:
http://www.cplusplus.com/reference/condition_variable/condition_variable/wait/
wait实现了线程的阻塞,不是通过线程锁的锁定而实现的,是通过线程锁的释放而实现的,或者换句话说,wait造成的阻塞和输入线程锁一点物理关系都没有,wait函数先丢弃锁,然后再阻塞线程,当外界调用唤醒函数notify_one/all的时候,线程解除阻塞,再获得锁的控制。但是这个时候,有可能锁是被别的线程拿着的,唤醒线程取不到锁控制。
这个时候就造成了我明明通知线程唤醒,但是线程却由于锁没有拿到而接着阻塞在那里,形成假唤醒(spurious wake-up calls)。
为了避免这个情况,提供了这个while,wait函数仅在ready为false的情况下,才会导致线程因为wait而阻塞,并且notifications只能在ready为true的情况下解除阻塞,如果ready不为true,那么当notification导致线程开始运转的时候,会接着在while里循环,再次进入等待notifation的状态。这个ready作为一个是否可以激活线程的一个总闸的作用,是个状态量,而notification是一个触发量。这对于检查假唤醒是十分有用的一个手段。
这也是wait的第二种调用方法,输入为一个已经获得锁和一个返回为bool量的函数(更广义点 可调用的返回为bool的对象)。

 

一个小小的锁都能有深刻和复杂的理解,也可以看见,深刻理解C++11对头发是有多不好了。。。
 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值