写在前面
C++并发编程实战的三四章内容的总结.
数据竞争(条件竞争)
看下面的多线程读写示例
#include <iostream>
#include <thread>
using namespace std;
int i{};
void f() {
    int num = 10000;
    for (int n{}; n < num; ++n) i = i + 1;
}
int main(int argc, char *argv[]) {
    thread t1(f);
    thread t2(f);
    t1.join();
    t2.join();
    cout << i << endl; // 10719
    return 0;
}
结果并不是 20000, 因为多线程会竞争读写访问的数据, 导致实际写入次数少于循环数量.
下面的互斥量, 条件变量等都可以用来解决线程数据的同步问题.
互斥
互斥量
mutex
lock_guard
互斥锁引发的死锁问题
发生死锁的四个条件
同时满足下面的四个条件才会发生死锁
- 互斥条件: 多个线程不能同时使用一个资源
- 持有并等待条件: 线程已经持有的线程被别的线程访问, 别的线程只能阻塞
- 不可剥夺条件: 自己使用完资源之前, 资源不会被别的线程获取
- 环路等待条件: 两个(或多个)线程获取资源的顺序存在环路
不用互斥锁也会死锁吗: 会的. 看下面的例子:
void bar(); void foo() { thread t1(bar); t1.join(); cout << "foo done" << endl; } void bar() { thread t2(foo); t2.join(); cout << "bar done" << endl; } int main() { bar(); foo(); return 0; }互相等待对方结束的两个线程.
事实上这个程序会抛出异常
system_error, 不会一直等待.what(): Resource temporarily unavailable
下面是使用 mutex 导致死锁的实例
单线程死锁
#include <mutex>
#include <iostream>
#include <thread>
using namespace std;
void t1();
void t2();
mutex m1, m2;
// 单线程死锁
void t1() {
    lock_guard<std::mutex> l1(m1);
    t2();
}
void t2() {
    lock_guard<std::mutex> l2(m2);
    t1();
}
int main(int argc, char *argv[]) {
    t1();
    t2();
    return 0;
}
多线程死锁
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1, mutex2;
void ThreadA() {
    mutex1.lock();
    std::cout << "Thread A has mutex1" << std::endl;
    // 休眠 1s,模拟线程 A 执行其他操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    // 尝试获取 mutex2 锁
    std::cout << "Thread A is waiting for mutex2" << std::endl;
    mutex2.lock();
    std::cout << "Thread A has mutex2" << std::endl;
    mutex2.unlock();
    mutex1.unlock();
}
void ThreadB() {
    mutex2.lock();
    std::cout << "Thread B has mutex2" << std::endl;
    // 休眠 1s,模拟线程 B 执行其他操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    // 尝试获取 mutex1 锁
    std::cout << "Thread B is waiting for mutex1" << std::endl;
    mutex1.lock();
    std::cout << "Thread B has mutex1" << std::endl;
    mutex1.unlock();
    mutex2.unlock();
}
int main() {
    std::thread threadA(ThreadA);
    std::thread threadB(ThreadB);
    threadA.join();
    threadB.join();
    return 0;
}
unique_lock
灵活加锁, 但是也耗费一定的资源. (相较于 lock_guard 而言)
不一定始终占有与之关联的互斥量.
具有更细粒度的控制级别
#include <thread>
#include <mutex>
class some_big_object {};
void swap(some_big_object& lhs, some_big_object& rhs);
class X {
private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd) : some_detail(sd) {}
    // 执行内部数据互换(避免死锁)
    // c++17 scoped_lock version
    friend void swap(X& lhs, X& rhs) {
        if (&rhs == &lhs) return;
        std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock);
        std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock);
        std::lock(lock_a, lock_b);
        swap(lhs.some_detail, rhs.some_detail);
    }
};
- defer_lock: 将互斥保留为未加锁状态.
- std::lock(): 此时才开始上锁.
双检查锁初始化资源的问题
shared_lock
共享锁(读锁)
多个线程可以同时锁住同一个
std::shared_mutex.
递归加锁
同步
条件变量(condition_variable)
利用条件变量等待事件完成
期值(future)
获取异步任务的返回值
#include <future>
#include <iostream>
using namespace std;
int find_ans() { return 42; }
int main(int argc, char *argv[]) {
    // async 和 thread 的创建方式类似, 都是先传入函数签名(可调用对象), 然后传入函数参数
    future<int> ans = std::async(find_ans);
    cout << "ans is " << ans.get() << endl; // ans is 42
    return 0;
}