Skip to main content
  1. Posts/

[C++11]std::condition_variable notes

·776 words·2 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。

用人话来说,condition_variable,也就是条件变量,是线程间通信的一种方式。

线程之间在很多时候需要通信,比如经典的生产者消费者问题

一个比较 naive 的方案是,用 mutex 来保护一个 flag,然后另一线程不停地 check 这个 flag 的状态是否改变。以及在这个方案上的改进:让另一个线程 check 之后,可以先睡一段时间。

但是这两种方法都不够好。第一种不好的原因当然是不停地 check,肯定会耗费大量的资源。而第二种,由于没办法准确估计要休眠的时间,因此不够实际。

这个时候我们可以考虑使用条件变量。

条件变量可以用在如下场景:一个或者多个线程在等某个条件成立,而这个条件由另外的线程所控制。当该条件成立时,控制该条件的线程会主动通知这些线程,将它们唤醒。

如下是一个最简单的例子:

 1std::mutex mut;
 2std::queue<data_chunk> data_queue;  // 1
 3std::condition_variable data_cond;
 4
 5void data_preparation_thread()
 6{
 7  while(more_data_to_prepare())
 8  {
 9    data_chunk const data=prepare_data();
10    std::lock_guard<std::mutex> lk(mut);
11    data_queue.push(data);  // 2
12    data_cond.notify_one();  // 3
13  }
14}
15
16void data_processing_thread()
17{
18  while(true)
19  {
20    std::unique_lock<std::mutex> lk(mut);  // 4
21    data_cond.wait(
22         lk,[]{return !data_queue.empty();});  // 5
23    data_chunk data=data_queue.front();
24    data_queue.pop();
25    lk.unlock();  // 6
26    process(data);
27    if(is_last_chunk(data))
28      break;
29  }
30}

接下来是一个较为复杂的例子:一个线程安全队列的实现。

详细代码
 1#include <queue>
 2#include <memory>
 3#include <mutex>
 4#include <condition_variable>
 5
 6template<typename T>
 7class threadsafe_queue
 8{
 9private:
10  mutable std::mutex mut;  // 1 互斥量必须是可变的
11  std::queue<T> data_queue;
12  std::condition_variable data_cond;
13public:
14  threadsafe_queue()
15  {}
16  threadsafe_queue(threadsafe_queue const& other)
17  {
18    std::lock_guard<std::mutex> lk(other.mut);
19    data_queue=other.data_queue;
20  }
21
22  void push(T new_value)
23  {
24    std::lock_guard<std::mutex> lk(mut);
25    data_queue.push(new_value);
26    data_cond.notify_one();
27  }
28
29  void wait_and_pop(T& value)
30  {
31    std::unique_lock<std::mutex> lk(mut);
32    data_cond.wait(lk,[this]{return !data_queue.empty();});
33    value=data_queue.front();
34    data_queue.pop();
35  }
36
37  std::shared_ptr<T> wait_and_pop()
38  {
39    std::unique_lock<std::mutex> lk(mut);
40    data_cond.wait(lk,[this]{return !data_queue.empty();});
41    std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
42    data_queue.pop();
43    return res;
44  }
45
46  bool try_pop(T& value)
47  {
48    std::lock_guard<std::mutex> lk(mut);
49    if(data_queue.empty())
50      return false;
51    value=data_queue.front();
52    data_queue.pop();
53    return true;
54  }
55
56  std::shared_ptr<T> try_pop()
57  {
58    std::lock_guard<std::mutex> lk(mut);
59    if(data_queue.empty())
60      return std::shared_ptr<T>();
61    std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
62    data_queue.pop();
63    return res;
64  }
65
66  bool empty() const
67  {
68    std::lock_guard<std::mutex> lk(mut);
69    return data_queue.empty();
70  }
71};

以及,虽然现在这样线程安全的数据结构应该早就有人封装好了,我们直接使用就行,比如之前用过的 intel tbb 的 concurrent_queue。不过如果对内部实现一无所知,大概没办法感觉到这种精巧之处吧..

参考资料:

std::condition_variable

std::condition_variable

以及借鉴了 c++并发编程第四章中格式化后的代码

Related

[C++11] promise && future leanrning notes

·322 words·1 min
用人话就是,主线程传给附属线程一个promise Object,然后主线程想要获取附属线程set给promise Object的值(也就是该线程返回的某个结果),需要通过主线程中的promise object 得到对应的future object(每个promise 对应一个 future),然后调用future 的get方法。如果附属线程没有执行作为参数传入的promise的set方法去返回结果,那么程序就会block住。