一 竞态条件(竞争条件)
定义:多个线程同时读写同一份共享数据,没有加任何保护,最终计算结果错乱。
原理:一条变量自增cnt++在CPU不是一条指令,会拆分成三步:读->修改->写回。
两个线程并发执行,步骤穿插打乱,就会出现数据错误。
例子:cnt初始=0,两个线程都执行cnt++,预期变成2,实际可能结果为1。
解决方式:互斥锁mutex,原子锁atomic。
二 std::thread
C++11线程类,用来创建并启动子线程,执行任务。
t.join():主线程阻塞等待子线程执行完毕,回收线程资源;主线程不等完不能往下走;
t.detach():分离线程,子线程后台独立跑,主线程不用等待;风险:主线程退出,子线程直接被杀死。
#include <iostream>
#include <thread>
using namespace std;
void task()
{
cout << "子线程运行" << endl;
}
int main()
{
thread t(task); // 创建线程,立刻启动
t.join(); // 等待子线程结束
cout << "主线程结束" << endl;
return 0;
}
三 stad::mutex互斥锁
作用:保护共享资源,同一时刻只允许一个线程拿到锁,访问临界区代码,避免竞态条件。
lock():尝试获取锁;锁被占用时,当前线程阻塞休眠,等别人释放。
unlock():释放锁,其他等待线程可以枪锁。
缺点:手动lock,unlock很危险。如果中间代码return或者抛出异常,unlock没有机会执行->死锁。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int cnt = 0;
void add()
{
mtx.lock(); // 手动上锁
cnt++; // 临界区,同一时间只能一个线程执行
mtx.unlock(); // 手动解锁
}
int main()
{
thread t1(add);
thread t2(add);
t1.join();
t2.join();
cout << cnt << endl;
return 0;
}
风险演示(会造成死锁)
void add()
{
mtx.lock();
cnt++;
if (cnt > 0)
return; // 函数直接return,下面unlock执行不到!
mtx.unlock();
}
一旦return或者抛出异常,unlock不会执行,锁永久占用->死锁。
四 std::lock_guard ***高频(RAII)
RAII思路:构造函数上锁,析构函数自动解锁。
创建lock_guard对象自动lock();对象离开作用域(花括号结束,return,异常),自动析构调用unlock()。
不需要手动写unlock,避免忘记释放锁造成死锁,工程代码首选。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int cnt = 0;
void add()
{
// 创建lg对象,构造函数自动lock()
lock_guard<mutex> lg(mtx);
cnt++;
// 就算这里return / throw异常,lg出作用域会自动析构,调用unlock
// 离开{},lg销毁,自动解锁
}
int main()
{
thread t1(add);
thread t2(add);
t1.join();
t2.join();
cout << cnt << endl;
return 0;
}
原理:对象生命周期绑定作用域,不用手动写unlock,规避忘记释放锁的问题;
五 std::condition_variable条件变量(生产者消费者)
作用:线程等待,唤醒,避免忙等。
必须配合mutex一起使用。
wait(mutex):释放锁,线程休眠等待;被唤醒后;重新抢占锁。继续执行。
wait做的两件关键事: ① 调用 wait 的时候自动释放 mutex,别的线程(生产者)才有机会拿 到锁修改条件; ② 收到 notify 唤醒后,重新抢占 mutex,拿到锁之后,再去判断条件。
notify_one():唤醒一个正在等待的线程。
notify_all():唤醒全部等待的线程。
经典场景:生产者消费者模型
生产者往队列放数据;队列空的时候,消费者调用wait()休眠,不一直循环耗CPU。生产者生产完数据调用notify,唤醒消费者取数据。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
mutex mtx;
condition_variable cv;
queue<int> q;
// 生产者:往队列放数据
void producer()
{
for(int i = 1; i <= 3; i++)
{
unique_lock<mutex> lock(mtx);
q.push(i);
cout << "生产:" << i << endl;
cv.notify_one(); // 唤醒1个等待的消费者
}
}
// 消费者:从队列取数据
void consumer()
{
while(true)
{
unique_lock<mutex> lock(mtx);
// 队列空,等待
cv.wait(lock, [&](){ return !q.empty(); });
int val = q.front();
q.pop();
cout << "消费:" << val << endl;
}
}
int main()
{
thread t1(producer);
thread t2(consumer);
t1.join();
t2.detach();
return 0;
}
六 std::atomic原子变量(无锁)
无锁,硬件层面保证操作不可拆分(原子)
简单变量读写,自增不用mutex,不会产生竞态条件,性能比锁高。
局限:只适合单个简单变量操作,复杂业务逻辑,多行代码的临界区,还是要mutex。
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> cnt = 0; // 原子变量
void add()
{
cnt++; // 硬件保证自增是原子操作,不用加锁,不会竞态
}
int main()
{
thread t1(add);
thread t2(add);
t1.join();
t2.join();
cout << cnt << endl;
return 0;
}
优点:无锁,性能高于 mutex
局限:只能处理单个变量简单操作。 如果有多行代码、多个变量需要保护,atomic 就不够用,必须 mutex。 举例:if(cnt >0) cnt–; 这两行不是原子的,atomic 无法保护。
网硕互联帮助中心



评论前必须登录!
注册