使用目的:保护共享资源,防止多线程并发读写造成数据错乱
基础概念:
临界区:多线程会访问共享变量的那段代码
竞态:多个线程并发访问共享数据,至少有一个线程是写操作;最终结果取决于线程调度的先后顺序,结果不确定
锁的作用:保证同一时刻最多只有一个线程进入临界区
互斥锁:
解释:互斥就是互相排斥,保证同时最多一个线程进入临界区
基础原型:
#include <mutex> std::mutex mtx; // 创建一把互斥锁对象原始成员函数:
mtx.lock():加锁
如果锁空闲 ->拿到锁,往下执行
如果锁已被别的线程持有 ->当前线程阻塞,原地等待直到锁被释放
mtx.unlock():解锁
释放锁,让其他等待这个锁的线程有机会获取
注意:不推荐直接手写lock() unlock():
// 危险写法! mtx.lock(); // 临界区代码,如果这里抛异常,unlock不会执行!锁永远不会释放,死锁! mtx.unlock();C++提供 RAII 包装器:lock_guard/unique_lock自动解锁
lock_guard:
{ // 构造的时候自动调用 lock() std::lock_guard<std::mutex> lock(mtx); // =========临界区开始======== // 只有拿到锁的线程才能执行这里 // 读写共享变量 // =========临界区结束======== } // 离开大括号,lock对象析构,自动调用 unlock()特点:
1.构造上锁,析构解锁,安全
2.不能手动解锁,锁的生命周期和大括号绑定
unique_lock:
std::unique_lock<std::mutex> lk(mtx); lk.unlock(); // 支持手动提前释放锁 // ... lk.lock(); // 可以再次上锁特点:
1.不强制需要大括号,锁被销毁后自动解锁
2.支持手动解锁,延迟加锁,尝试加锁
3.条件变量condition_variable使用必须搭配unique_lock