在C++并发编程里,多个线程同时读写同一块共享内存是最容易出错的地方。如果不加控制,就会出现数据竞争,导致计算结果错误甚至程序崩溃。C++标准库提供了多种机制来安全地管理共享内存,核心思路是限制同一时间能访问数据的线程数量,或者让某些操作不可中断。
使用互斥锁保护共享内存
互斥锁(std::mutex)是最基础的同步工具。把共享内存的访问放在锁的范围内,就能保证同一时刻只有一个线程进入临界区。
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int shared_counter = 0;
std::mutex mtx;
void worker(int times) {
for (int i = 0; i < times; ++i) {
// 加锁保护共享内存
mtx.lock();
++shared_counter;
mtx.unlock();
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(worker, 1000);
}
for (auto& t : threads) {
t.join();
}
std::cout << "final counter: " << shared_counter << std::endl;
return 0;
}
上面代码中,shared_counter 就是被多个线程共享的内存。通过 mtx 的 lock 和 unlock,保证了自增操作的原子性。
使用原子类型避免锁开销
对于简单的计数或标志位,可以使用 std::atomic 类型。它不需要互斥锁,由硬件指令保证操作不可分割。
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
std::atomic<int> atomic_counter(0);
void worker(int times) {
for (int i = 0; i < times; ++i) {
// 原子自增,无需加锁
atomic_counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(worker, 1000);
}
for (auto& t : threads) {
t.join();
}
std::cout << "atomic final: " << atomic_counter.load() << std::endl;
return 0;
}
用条件变量协调线程访问
当线程之间需要等待某个共享状态变化时,可以配合 std::condition_variable 使用。它让线程在条件不满足时休眠,避免忙等。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex cv_mtx;
std::condition_variable cv;
bool data_ready = false;
void consumer() {
std::unique_lock<std::mutex> lock(cv_mtx);
// 等待共享标志变为 true
cv.wait(lock, [] { return data_ready; });
std::cout << "consumer got data" << std::endl;
}
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(cv_mtx);
data_ready = true;
}
cv.notify_one();
}
int main() {
std::thread t1(consumer);
std::thread t2(producer);
t1.join();
t2.join();
return 0;
}
管理建议对比
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 互斥锁 | 复杂结构或多次访问 | 易理解,通用 | 有锁竞争开销 |
| 原子操作 | 简单变量 | 无锁,高效 | 仅支持有限类型 |
| 条件变量 | 线程等待事件 | 避免忙等 | 必须配锁使用 |
实际项目中,应尽量减少共享内存的范围,明确所有权。例如通过 std::unique_ptr 转移资源,或用消息队列代替裸共享内存,能从设计上降低并发风险。