C++11开始支持多线程编程,并在之后的版本中不断完善。
Hello World单线程写法:
1 |
|
Hello World多线程写法:
1 |
|
量产线程的写法:
1 |
|
使用 std::lock_guard
保护共享数据:
C++中通过实例化std::mutex
创建互斥量实例,通过成员函数 lock()
对互斥量上锁,unlock()
进行解锁。不过,实践中不推荐直接去调用成员函数,调用成员函数就意味着,必须在每个函数出口都要去调用 unlock()
,也包括异常的情况。C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard
,在构造时就能提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁互斥量能被正确解锁。
1 |
|
使用 std::shared_timed_mutex
避免数据竞争
shared_timed_mutex
类是能用于保护数据免受多个线程同时访问的同步原语。与其他促进排他性访问的互斥类型相反,拥有二个层次的访问:
- 共享 - 多个线程能共享同一互斥的所有权。
- 排他性 - 仅一个线程能占有互斥。
共享互斥通常用于多个读线程能同时访问同一资源而不导致数据竞争,但只有一个写线程能访问的情形。
1 |
|
使用 std::scoped_lock
避免死锁
线程有对锁的竞争:一对线程需要对他们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个解锁。这样没有线程能工作,因为他们都在等待对方释放互斥量。这种情况就是死锁,它的最大问题就是由两个或两个以上的互斥量来锁定一个操作。
C++标准库有办法解决这个问题,std::scoped_lock
——可以一次性锁住多个(两个以上)的互斥量,并且没有副作用(死锁风险),而且是RAII风格喵~。
以下示例用 std::scoped_lock
锁定互斥对而不死锁,且为 RAII 风格。
1 |
|
使用更为灵活的std::unique_lock
std::unqiue_lock
使用更为自由的不变量,这样 std::unique_lock
实例不会总与互斥量的数据类型 相关,使用起来要比 std:lock_guard
更加灵活。首先,可将 std::adopt_lock
作为第二个参数传入 构造函数,对互斥量进行管理;也可以将 std::defer_lock
作为第二个参数传递进去,表明互斥量应 保持解锁状态。这样,就可以被 std::unique_lock
对象(不是互斥量)的 lock()
函数所获取,或传递 std::unique_lock
对象到 std::lock()
中。
当你想要锁定互斥锁时,可以创建类型为 std::unique_lock
的局部变量,并将该互斥锁作为参数传递。 构造unique_lock时,它将锁定互斥锁,并且销毁该互斥锁后,它将解锁该互斥锁。 更重要的是:如果引发异常,则将调用 std::unique_lock
析构函数,因此互斥量将被解锁。
1 | // 示例 1 |
1 | // 示例 2 |
小结
我们现在会用C++写多线程代码啦,但是如何避免死锁,何种情况该用std::lock_guard
、std::shared_timed_mutex
、std::scoped_lock
、std::unique_lock
,仍需要多加练习噢。
Author: 樱花雨