C++线程同步的几种方式
1. 引言
在多线程编程中,当多个线程需要访问和修改共享资源时,如果不加以控制,可能会导致数据不一致、竞态条件等问题。线程同步是确保多个线程在访问共享资源时能够正确、有序执行的关键技术。本文将系统性地介绍几种主流的线程同步方式及其适用场景。
2. 线程同步的核心方式
2.1 互斥锁 (Mutex)
互斥锁是最基本、最常用的同步机制。它保证在同一时刻只有一个线程可以进入临界区(访问共享资源的代码段)。
- 原理:线程在进入临界区前尝试获取锁,若锁已被占用则阻塞等待;离开临界区时释放锁。
- 特点:简单、通用,但可能引发死锁(如多个锁的获取顺序不当)。
// C++ std::mutex 实现互斥
#include <iostream>
#include <thread>
#include <mutex>
class Counter {
private:
int count = 0;
std::mutex mtx;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
count++;
}
int getCount() const { return count; }
};2.2 信号量 (Semaphore)
信号量是一种计数器,用于控制同时访问特定资源的线程数量,允许多个线程(数量由信号量值决定)同时进入临界区。
- 原理:初始化一个许可数量。线程执行
acquire()获取许可(数量减1),执行完毕调用release()归还许可(数量加1)。当许可数为0时,后续线程将阻塞。 - 适用场景:资源池管理(如数据库连接池)、流量控制。
// C++ 信号量示例 (C++20 引入 std::counting_semaphore)
#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<10> semaphore(3); // 允许3个线程同时访问
void accessResource() {
semaphore.acquire();
try {
// 访问共享资源
std::cout << "Thread accessing resource" << std::endl;
} catch (...) {
semaphore.release();
throw;
}
semaphore.release();
}2.3 条件变量 (Condition Variable)
条件变量通常与互斥锁结合使用,允许线程在某个条件不满足时主动释放锁并进入等待状态,直到其他线程改变条件后将其唤醒。
- 原理:线程在持有锁的情况下检查条件,若条件不满足则调用
await()进入等待并释放锁;其他线程改变条件后调用signal()或signalAll()唤醒等待线程。 - 适用场景:生产者-消费者模型、线程间协作。
// C++ 使用 std::mutex 和 std::condition_variable
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool conditionMet = false;
void workerThread() {
std::unique_lock<std::mutex> lock(mtx);
while (!conditionMet) {
cv.wait(lock); // 释放锁并等待
}
// 条件满足,执行操作
std::cout << "Condition met, proceeding..." << std::endl;
}2.4 读写锁 (ReadWriteLock)
读写锁将访问分为读操作和写操作,允许多个读线程同时访问共享资源,但写线程必须独占访问。
- 原理:读锁是共享的,写锁是排他的。当有线程持有写锁时,其他所有读写线程都被阻塞;当没有写锁时,多个读线程可以同时获取读锁。
- 适用场景:读多写少的场景,如缓存、配置信息。
// C++ std::shared_mutex 示例 (C++17)
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex rwLock;
int sharedData = 0;
void reader() {
std::shared_lock<std::shared_mutex> lock(rwLock);
// 读取数据
std::cout << "Reading data: " << sharedData << std::endl;
}
void writer() {
std::unique_lock<std::shared_mutex> lock(rwLock);
// 修改数据
sharedData++;
std::cout << "Writing data: " << sharedData << std::endl;
}2.5 屏障 (Barrier) 与 闭锁 (Latch)
闭锁 (CountDownLatch):允许一个或多个线程等待其他线程完成一组操作。计数器初始化后递减,减到0时等待的线程被释放。
屏障 (CyclicBarrier):让一组线程互相等待,直到所有线程都到达某个屏障点后才继续执行。可重复使用。
// C++ std::latch 示例 (C++20)
#include <iostream>
#include <thread>
#include <latch>
std::latch latch(3); // 需要3个线程完成
void worker() {
// 执行任务
std::cout << "Worker thread completing task" << std::endl;
latch.count_down(); // 计数器减1
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
std::thread t3(worker);
latch.wait(); // 主线程等待,直到计数器为0
t1.join();
t2.join();
t3.join();
return 0;
}3. 选择同步方式的考量因素
- 性能开销:锁的粒度(粗粒度锁 vs 细粒度锁)、锁竞争程度。
- 死锁风险:避免循环等待、按固定顺序获取锁、使用带超时的锁。
- 业务场景:读多写少用读写锁;线程协作用条件变量;控制并发数用信号量。
- 编程复杂度:
synchronized简单但不够灵活;Lock接口提供更多控制(如可中断、超时、公平性)。
4. 总结
线程同步是并发编程的基石。理解并正确使用互斥锁、信号量、条件变量、读写锁、屏障/闭锁等机制,可以帮助开发者构建出既高效又安全的多线程程序。在实际开发中,应根据具体场景选择最合适的同步工具,并时刻警惕死锁、活锁、饥饿等并发问题。
到此这篇关于C++线程同步的j几种方式的文章就介绍到这了,更多相关C++线程同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论