1. 优先使用基于任务的编程而非基于线程的编程
这里的意思是,优先使用std::async(),而非使用std::thread,来创建异步的任务。
因为大部分情况下,我们只是简单的执行异步任务罢了。
std::async()的优点:
-
代码简单,能自然获取异步任务结果或者异常
-
异步任务的调度交给了标准库来调度,避免手动管理线程
只有在下面情况下,建议直接使用std::thread:
-
需要直接访问线程的API
-
需要且能够优化线程的使用
-
需要实现超越线程API的功能
2. 若有异步任务需要,必须指定std::launch::async
std::async()有2个选项:
-
std::launch::async:任务必须在不同线程异步执行 -
std::launch::deferred:任务延迟到future上调用get/wait时才执行
默认配置是它们的或值,即std::launch::async | std::launch::deferred,既不同线程,且延迟执行。
所以,默认情况下,轮询future::wait_for()可能返回std::future_status::deferred,且一直如此,所以需要对此进行判断。否则,若任务一定要马上异步执行,一定得显式指定std::launch::async。
3. 让std::thread到最后都unjoinable
Unjoinable的线程包括:
-
默认构造
std::thread,没有任务执行 -
被移动走的
std::thread -
已经
join的std::thread -
已经
detach的std::thread:父线程无法再控制子线程
std::thread销毁时不会隐式join或detach,因为可能导致表现异常(如不必要的等待,对于前者)或者未定义行为(悬挂数据等,对于后者):
- 所以,销毁unjoinable的
std::thread,会导致程序直接中止。
所以必须保证std::thread销毁前,必须unjoinable。
此外,尽量把std::thread放在成员变量的最后,保证之前的成员都已经初始化完成后,再初始化线程。
4. 关注不同线程句柄的析构行为
上节说,销毁joinable的std::thread直接导致程序中止。
而销毁joinable的std::future不会导致程序中止,因为它只销毁std::future本身:
-
调用的结果放置在一块共享区域内

-
若存在调用方,
std::shared_future难以实现 -
若存在被调用方,由于结果是局部的,被调用方被销毁时,结果会被销毁
-
所以放在共享区域内
-
-
若引用了共享状态,那么销毁时,会被阻塞住,等同于隐式
join
5. 对于单次事件通信,使用void的future
A任务做完后,通知B任务继续做。
一种实现方式是std::condition_variable,但要注意:
-
需要获取一个锁,配套条件变量使用
-
注意虚假唤醒,即
wait()需要传递一个Lambda函数,判断条件为真后,才能被唤醒,即如同下面的处理:1
cond.wait([]() { return ready; });
上面的代码等同于循环轮询,以避免虚假唤醒,如下所示:
1 2 3
while (!ready) { cond.wait(); }
另一种实现方式是使用std::future和std::promise,B等待A完成的信号:
-
A完成后,通过
std::promise来设置一个信号,调用set_value() -
B等待,需要通过
std::promise调用get_future().wait()等待信号
上述特点:
-
是1P1C的模型,仅适用于一次通信
-
使用了共享存储状态,它存储在堆中,有开销
-
由于是一次通信,
std::future和std::promise的模板参数可以是void。
6. 并发使用std::atomic,特殊内存使用volatile
C++std::atomic等同于Java的Atomic类,数据操作是原子的。
std::atomic不支持拷贝和移动,需要通过load()和store()传递值
C++volatile和Javavolatile不同:
-
C++:没有并发含义,只是告诉编译器它是特殊内存(例如外部设备等),不要优化它的读写,例如
-
冗余多次读取同一个变量,不要优化为读1次
-
冗余多次写如同一个变量,不要优化为写1次
auto y = x的auto会把volatile拿掉(同样const也会拿掉,若x是非引用非指针) -
-
Java:保证数据各线程立即可见,采用内存屏障实现,避免了指令重排
- C++的
std::atomic也能做到这点
- C++的
总之:
-
std::atomic对并发有用,对特殊内存没用 -
volatile对特殊内存有用,对并发没用