在C++中,保证线程安全有如下几种机制:
1. 互斥锁(Mutex)
互斥锁用于保护共享资源,确保同一时间只有一个线程可以访问:
#include <mutex>
#include <thread>
std::mutex mtx; // 全局互斥锁
void sharedFunction() {
mtx.lock(); // 获取锁
// ... 访问或修改共享资源 ...
mtx.unlock(); // 释放锁
}
void threadSafeFunction() {
std::lock_guard<std::mutex> lock(mtx); // C++11 锁 guard
// 自动在作用域结束时释放锁
// 访问或修改共享资源
}
2. 原子操作
原子操作保证单个操作的原子性,无需使用锁:
#include <atomic>
#include <thread>
std::atomic<int> atomicVar(0); // 初始化原子变量
void incrementFunction() {
atomicVar.fetch_add(1, std::memory_order_relaxed); // 原子增加
}
3. 条件变量
条件变量用于线程间的同步:
#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex cv_m;
std::condition_variable cv;
bool ready = false;
void workerThread() {
std::unique_lock<std::mutex> lock(cv_m);
cv.wait(lock, []{ return ready; }); // 等待条件变量通知
// 条件满足后的工作...
}
void mainThread() {
{
std::lock_guard<std::mutex> lock(cv_m);
// 准备数据...
ready = true;
} // 离开作用域,解锁
cv.notify_one(); // 通知一个等待的线程
}
线程不安全是如何出现的:
线程不安全通常由以下情况引起:
- 数据竞争:多个线程同时读写同一个未受保护的内存位置。
- 不一致的共享数据:多个线程基于共享数据做出决策,但没有适当的同步。
- 错误的锁使用:例如,死锁、锁的顺序不一致等。
处理方案:
- 避免共享可变状态:尽可能使用线程局部变量。
- 使用锁:保护共享资源的访问。
- 使用原子类型:避免使用锁,适用于简单的数据操作。
- 使用读写锁:允许多个读者同时访问,但写入时需要独占访问。
- 使用条件变量:在某些需要线程间协调的场景。
示例:线程不安全示例及解决方案
线程不安全的代码:
int counter = 0;
void unsafeIncrement() {
counter++; // 这里存在数据竞争
}
使用互斥锁解决线程不安全:
std::mutex mtx;
void safeIncrement() {
mtx.lock();
counter++;
mtx.unlock();
}
或者使用std::lock_guard
简化锁的管理:
void saferIncrement() {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
使用原子操作进一步改进:
std::atomic<int> counter(0);
void atomicIncrement() {
counter.fetch_add(1, std::memory_order_relaxed);
}