C++面试4-线程同步
| 分类 | 名称 | C++标准 | Windows API | POSIX/Linux | 功能描述 |
|---|---|---|---|---|---|
| 互斥锁 | std::mutex | ✅ | CRITICAL_SECTION / CreateMutex | pthread_mutex_t | 保证同一时刻仅一个线程访问共享资源 |
| 自旋锁 | std::atomic_flag | ✅ | - | pthread_spinlock_t | 忙等方式锁,适合短时间加锁 |
| 读写锁 | std::shared_mutex (C++17) | ✅ | SRWLock | pthread_rwlock_t | 多读单写并行 |
| 条件变量 | std::condition_variable | ✅ | ConditionVariable | pthread_cond_t | 等待条件满足再继续执行 |
| 信号量 | std::counting_semaphore (C++20) | ✅ | CreateSemaphore | sem_t | 控制同时访问共享资源的线程数 |
| 原子操作 | std::atomic<T> | ✅ | Interlocked* 系列 | __sync_fetch_and_add 等 | 无锁同步,轻量高效 |
| 事件 | - | ✅ CreateEvent | pthread_cond_signal 模拟 | 用于线程间通知或唤醒 | |
| 屏障 | std::barrier (C++20) | ✅ | - | pthread_barrier_t | 等所有线程到达同一点后一起继续 |
二、各类同步机制示例
1️⃣ 互斥锁(mutex)
✅ C++ 标准版
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void add() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自动加解锁
++counter;
}
}
int main() {
std::thread t1(add), t2(add);
t1.join(); t2.join();
std::cout << "counter = " << counter << std::endl;
}
counter = 200000
#include <windows.h>
#include <iostream>
Window 版本:
CRITICAL_SECTION cs;
int counter = 0;
DWORD WINAPI add(LPVOID) {
for (int i = 0; i < 100000; ++i) {
EnterCriticalSection(&cs);
++counter;
LeaveCriticalSection(&cs);
}
return 0;
}
int main() {
InitializeCriticalSection(&cs);
HANDLE t1 = CreateThread(nullptr, 0, add, nullptr, 0, nullptr);
HANDLE t2 = CreateThread(nullptr, 0, add, nullptr, 0, nullptr);
WaitForMultipleObjects(2, new HANDLE[2]{t1, t2}, TRUE, INFINITE);
std::cout << "counter = " << counter << std::endl;
DeleteCriticalSection(&cs);
}
2 条件变量(condition_variable)
#include <condition_variable>
#include <mutex>
#include <thread>
#include <queue>
#include <iostream>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
bool done = false;
void producer() {
for (int i = 0; i < 5; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
q.push(i);
}
cv.notify_one();
}
done = true;
cv.notify_all();
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !q.empty() || done; });
if (!q.empty()) {
std::cout << "Consume: " << q.front() << "\n";
q.pop();
} else if (done) break;
}
}
int main() {
std::thread t1(producer), t2(consumer);
t1.join(); t2.join();
}
打印:
Consume: 0
Consume: 1
Consume: 2
Consume: 3
Consume: 4
3 信号量(semaphore)
#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<2> sem(2); // 最多允许2线程同时访问
void worker(int id) {
sem.acquire();
std::cout << "Thread " << id << " working\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
sem.release();
}
int main() {
std::thread t1(worker, 1), t2(worker, 2), t3(worker, 3);
t1.join(); t2.join(); t3.join();
}
4️⃣ 原子变量(atomic)
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter = 0;
void add() {
for (int i = 0; i < 100000; ++i)
counter.fetch_add(1);
}
int main() {
std::thread t1(add), t2(add);
t1.join(); t2.join();
std::cout << "counter = " << counter << std::endl;
}
5️⃣ 读写锁(shared_mutex)
#include <shared_mutex>
#include <thread>
#include <iostream>
std::shared_mutex rwlock;
int data = 0;
void reader(int id) {
std::shared_lock<std::shared_mutex> lock(rwlock);
std::cout << "Reader " << id << " reads " << data << "\n";
}
void writer(int id) {
std::unique_lock<std::shared_mutex> lock(rwlock);
++data;
std::cout << "Writer " << id << " updates to " << data << "\n";
}
int main() {
std::thread t1(writer, 1);
std::thread t2(reader, 2);
std::thread t3(reader, 3);
t1.join(); t2.join(); t3.join();
}
三、选择建议总结
| 场景 | 推荐同步方式 | 原因 |
|---|---|---|
| 数据访问互斥 | std::mutex / pthread_mutex_t | 经典安全 |
| 短时间锁竞争 | 自旋锁 / 原子操作 | 避免上下文切换 |
| 生产者-消费者 | 条件变量 + 队列 | 高效等待唤醒 |
| 多读少写数据 | 读写锁 | 读多线程并行 |
| 控制资源数量 | 信号量 | 可同时运行 N 个线程 |
| 多线程阶段同步 | 屏障 std::barrier | 阶段性同步 |
| 事件触发/通知 | 条件变量 / Windows Event | 明确触发唤醒机制 |
Writer 1 updates to 1
Reader 2 reads 1
Reader 3 reads 1
这是理解多线程同步机制的重要一环。我们来一步步详细拆解:
一、首先复习一下:锁的目的
多线程编程中,多个线程同时访问共享资源(变量、文件、内存等)时,会产生数据竞争(Data Race)和不一致性(Inconsistency)。
为防止这种情况,我们用各种同步原语(Synchronization Primitives)来控制访问顺序:
std::mutex:互斥锁std::recursive_mutex:可重入互斥锁std::timed_mutex:带超时的互斥锁std::shared_mutex(C++17):读写锁(shared-exclusive lock)二、读写锁(shared_mutex)是什么?
读写锁是一种特殊的互斥机制,它区分了两种访问:
| 类型 | 权限 | 是否互斥 | 适用场景 |
|---|---|---|---|
| 读锁(shared lock) | 只读访问 | 多个线程可以同时加读锁 | 多线程“读多写少” |
| 写锁(unique lock) | 修改访问 | 只能有一个线程加写锁,且此时不能有任何读锁 | 修改共享数据时 |
🧩 三、为什么要用读写锁,而不是普通互斥锁?
✅ 1. 提升并发性能
普通的 mutex 是完全互斥的:
线程 A 正在读取数据;
线程 B 想读,也得等;
明明两个线程都只读,没有写操作,其实不会互相影响,但
mutex仍强制串行化。
这会大大降低性能。
而 读写锁 允许:
多个线程同时读取,只在写入时互斥。
这对“读多写少”的场景(例如缓存查询、配置读取)性能提升明显。
✅ 2. 避免不必要的等待
std::shared_mutex rwLock;
int value = 0;
void reader(int id) {
std::shared_lock lock(rwLock); // 读锁
std::cout << "Reader " << id << " sees value = " << value << "\n";
}
void writer(int id) {
std::unique_lock lock(rwLock); // 写锁
++value;
std::cout << "Writer " << id << " modifies value = " << value << "\n";
}
如果我们用普通 std::mutex:
即使 10 个线程都只是读取
value,也得一个个排队;用
shared_mutex,这 10 个线程可以同时读。
✅ 3. 资源访问模式不同
互斥锁(mutex)保护的是「谁都不能同时访问」;
读写锁(rwlock)保护的是「谁不能同时写」。
| 场景 | 用互斥锁 | 用读写锁 |
|---|---|---|
| 只有一个房门,所有人都排队进 | ✅ 合理但慢 | ⚠️ 太保守 |
| 图书馆:很多人能同时看书,只有管理员能改书 | ❌ 阻塞所有人 | ✅ 高效 |
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <chrono>
std::shared_mutex rwLock;
int data = 0;
void reader(int id) {
for (int i = 0; i < 3; ++i) {
std::shared_lock<std::shared_mutex> lock(rwLock); // 读锁
std::cout << "Reader " << id << " reads data = " << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void writer(int id) {
for (int i = 0; i < 3; ++i) {
std::unique_lock<std::shared_mutex> lock(rwLock); // 写锁
++data;
std::cout << "Writer " << id << " writes data = " << data << "\n";
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
int main() {
std::vector<std::thread> threads;
// 1个写者,3个读者
threads.emplace_back(writer, 1);
for (int i = 0; i < 3; ++i)
threads.emplace_back(reader, i+1);
for (auto &t : threads)
t.join();
}
