Chapter 7 The Concurrency API
Item 35 Prefer task-based programming to thread-based
异步执行一个函数有两种方法:
std::thread
和std::async
:c++1
2
3
4
5int doAsyncWork();
std::thread t(doAsyncWork);
auto fut = std::async(doAsyncWork);async
相比thread
有如下优点:- 能更方便地获取返回值、捕获异常;
- 使用默认的 policy 可以将线程管理的任务交给系统。
Item 36 Specify std::launch::async if asynchronicity is essential
std::async
有两种 policy:async
和deferred
,前者确保异步执行,后者推迟执行直到调用async
的线程调用了get
或wait
。如果不指定 policy,则系统会根据负载自动指定。让系统来指定的话会有一些问题:
- 由于没有办法确定函数是不是在一个新的线程中执行,所以当函数读写一些 thread local 的变量时需要小心;
- 如果系统指定
deferred
,那么wait_for
的返回值也会是deferred
,需要将这一点考虑进来; - 如果系统指定
deferred
,并且没有get
或wait
,那么函数不会被执行。
必要的话需要手动指定 policy 为
async
。
Item 37 Make std::threads unjoinable on all paths
std::thread
有两个状态:joinable 和 unjoinable- joinable 是说
std::thread
和一个线程相对应,这个线程可以是处于等待执行、正在执行、阻塞或终止的状态; - unjoinable 是说
std::thread
不和任何一个线程相对应,有以下一些情况会导致一个std::thread
unjoinable:- 默认构造一个
std::thread
,没有传进去一个函数。std::thread
没有东西执行,自然就不会绑定到任何一个线程; - 被移动的
std::thread
,原先和该std::thread
绑定的线程被移动到了另一个std::thread
(std::thread
禁用拷贝); - 已经被
join
的std::thread
。调用join
方法会等待函数执行完成并回收线程; - 已经被
detach
的std::thread
。调用detach
方法强行断开std::thread
和某个线程的关联。
- 默认构造一个
- joinable 是说
如果一个 joinable
std::thread
的析构函数被调用,程序将会终止。因为程序无法决定使用隐式的join
还是隐式的detach
:- 如果使用隐式的
join
,会带来性能上的问题。因为析构std::thread
意味着其中执行的函数已经不再重要,再花费时间等待其执行完成就没有必要了。 - 如果使用隐式的
detach
,则会带来正确性上的问题。因为std::thread
在执行时往往会修改它存在的栈帧中的数据,而直接将其detach
然后继续执行主线程就有可能导致主线程的下一个栈帧和之前std::thread
的栈帧有重合部分,主线程中的数据就好像是莫名其妙被修改了一样。
好的解决方法是通知
std::thread
立刻停止执行其中的函数,但是 C++11 并不支持这一点。- 如果使用隐式的
程序员应当确保在任何一条执行路径中,
std::thread
最终处于 unjoinable 的状态。而手动保证这一点是很难的,但是可以借用 RAII 的概念,在std::thread
上封装一个类,在析构函数中将std::thread
的状态改变成 unjoinable。
Item 38 Be aware of varing thread handle destructor behavior
async
的 task 和std::thread
一样,都和一个线程相对应,它们称为 thread handle(线程句柄)- 但是它们析构时的行为不太一样,
future
析构时,大部分情况下就是直接析构,不join
也不detach
,只是将 shared state 的 reference count 减一;而当满足以下三个条件时,析构会阻塞住直到 task 执行完(就像被调了join
一样):- 这个
future
和std::async
创建的 shared state 相关联; std::async
的 policy 是async
(不管是指定的还是系统选择的);- 这个
future
是和这个 shared state 相关联的最后一个future
。
- 这个
Item 39 Consider void futures for one-shot event communication
使用 condition variable 和 flag 可以实现同步机制(detect - react,某一线程达到某个条件时,另一线程才能继续执行),但是实现的方式并不优雅(需要互斥锁、需要防止假醒等等)
使用
promise
和future
能达到类似的效果,但是缺点是只能做一次同步:c++1
2
3
4
5
6
7
8
9
10
11std::promise<void> p;
void react(); // func for reacting task
void detect() // func for detecting task
{
std::thread t([] {
p.get_future().wait(); // suspend
react();
});
p.set_value(); // awake
t.join();
}使用
shared_future
可以一次通知多个线程。
Item 40 Use std::atomic for concurrency, volatile for special memory
std::atomic
提供一种原子操作的手段,对atomic
变量的 RMW(Read - Modify - Write)操作是原子的;此外它还提供一种类似 barrier 的机制,即在atomic
操作前的语句不会被 reorder 到atomic
操作后。volatile
则是用来告诉编译器某块内存是特殊的,比如用于 memory-mapped I/O 的内存(和外围设备通信),这就决定了编译器对这些内存的读写操作不能做像正常内存那样的优化(比如多次读就合并为一次,多次写就只写最后一次)。