↓ 跳过正文
  1. Posts/

[C++11]std::condition_variable notes

·776 字·2 分钟

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++并发编程第四章中格式化后的代码

相关文章

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

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

c++11 function 与bind 学习笔记

·315 字·1 分钟
C++11 std::function 是一种通用、多态的函数封装,它的实例可以对任何可 以调用的目标实体进行存储、复制和调用操作 见下面的例子 1 /* *********************************************** 2 Author :111qqz 3 mail: renkuanze@sensetime.com 4 Created Time :2018年07月19日 星期四 17时41分00秒 5 File Name :bind.cpp 6 ************************************************ */ 7 #include <functional> 8 #include <iostream> 9 using namespace std; 10 float foo( int x,int y,int z){return x+y+z+1.;} 11 int main() 12 { 13 function<int(int,int)>func = foo; 14 int y = 10; 15 function<int(int)>fun = [&]( int value)->int 16 { 17 return 1+value+y; 18 }; 19 cout<<func(15,4,9)<<endl; 20 cout<<fun(8)<<endl; 21 return 0; 22 } std::bind 则是用来绑定函数调用的参数的,它解决的需求是我们有时候可 能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,我们可以将 部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调 用

C++ 记录代码运行时间

·169 字·1 分钟
以前用的办法太老土啦 看到一个since C++11的方法,我觉得比较优雅 1 #include <iostream> 2 #include <chrono> 3 //#include <ratio> 4 #include <thread> 5 6 void f() 7 { 8 std::this_thread::sleep_for(std::chrono::seconds(1)); 9 } 10 11 int main() 12 { 13 auto t1 = std::chrono::high_resolution_clock::now(); 14 f(); 15 auto t2 = std::chrono::high_resolution_clock::now(); 16 17 // floating-point duration: no duration_cast needed 18 std::chrono::duration<double, std::milli> fp_ms = t2 - t1; 19 20 // integral duration: requires duration_cast 21 auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1); 22 23 // converting integral duration to integral duration of shorter divisible time unit: 24 // no duration_cast needed 25 std::chrono::duration<long, std::micro> int_usec = int_ms; 26 27 std::cout << "f() took " << fp_ms.count() << " ms, " 28 << "or " << int_ms.count() << " whole milliseconds " 29 << "(which is " << int_usec.count() << " whole microseconds)" << std::endl; 30 }