15.多线程里线程的同步方式有哪些

2026-01-23
2321 字 · 12 分钟

🔬 多线程同步机制详解

📖 内容概览

本文详细介绍C++多线程编程中常用的线程同步方式,包括互斥锁、信号量、条件变量、屏障、读写锁、自旋锁和原子操作等机制,分析它们的工作原理、适用场景、优缺点,并通过代码示例演示具体使用方法。

🎯 核心概念

✨ 线程同步的必要性

在多线程环境中,多个线程同时访问共享资源可能导致数据竞争(Race Condition),从而产生不可预测的结果。线程同步机制的目的是确保多个线程能够安全地访问共享资源,避免数据不一致和程序崩溃。

🔧 同步方式分类

C++中常用的线程同步方式包括:

  1. 互斥锁(Mutex):保护临界区,确保同一时间只有一个线程访问
  2. 信号量(Semaphore):控制同时访问资源的线程数量
  3. 条件变量(Condition Variable):等待特定条件满足
  4. 屏障(Barrier):协调多个线程的执行顺序
  5. 读写锁(Read-Write Lock):区分读写操作,优化并发性能
  6. 自旋锁(Spin Lock):避免上下文切换开销
  7. 原子操作(Atomic Operation):无锁同步机制

🔍 详细介绍

🛡️ 互斥锁(Mutex)

基本概念:互斥锁是最常用的同步机制,用于保护临界区,确保同一时间只有一个线程可以进入临界区。 工作原理:线程在进入临界区前获取锁,离开时释放锁。如果锁已被其他线程占用,当前线程会阻塞等待。 适用场景:保护共享数据,避免多个线程同时修改 代码示例

#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁和解锁
shared_data++;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "共享数据最终值: " << shared_data << std::endl;
return 0;

📊 信号量(Semaphore)

基本概念:信号量是一种计数器,用于控制同时访问特定资源的线程数量。 工作原理:线程在访问资源前获取信号量(计数器减1),访问完毕后释放信号量(计数器加1)。当计数器为0时,后续线程会阻塞等待。 适用场景:限制并发访问的线程数量,如连接池、资源池

#include <iostream>
#include <thread>
#include <semaphore>
#include <vector>
#include <chrono>
std::counting_semaphore<3> sem(3); // 最多允许3个线程同时访问
void worker(int id) {
sem.acquire(); // 获取信号量
std::cout << "线程 " << id << " 开始工作" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "线程 " << id << " 完成工作" << std::endl;
sem.release(); // 释放信号量
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(worker, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}

🔄 条件变量(Condition Variable)

基本概念:条件变量用于线程间的通信,允许线程等待特定条件满足后再继续执行。 工作原理:线程在条件不满足时调用wait()进入等待状态,其他线程在条件满足时调用notify_one()或notify_all()唤醒等待线程。 适用场景:生产者-消费者模型、线程协作完成任务

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
std::condition_variable cv;
std::queue<int> data_queue;
bool done = false;
void producer() {
for (int i = 0; i < 10; ++i) {
{ // 临界区开始
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "生产数据: " << i << std::endl;
} // 临界区结束
cv.notify_one(); // 通知等待的消费者
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{ // 临界区开始
std::lock_guard<std::mutex> lock(mtx);
done = true;
} // 临界区结束
cv.notify_all(); // 通知所有等待的消费者
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return done || !data_queue.empty(); });
if (done && data_queue.empty()) {
break;
}
int data = data_queue.front();
data_queue.pop();
std::cout << "消费数据: " << data << std::endl;
}
}
int main() {
std::thread t_producer(producer);
std::thread t_consumer(consumer);
t_producer.join();
t_consumer.join();
return 0;
}

📍 屏障(Barrier)

基本概念:屏障用于协调多个线程的执行,确保所有线程都到达屏障点后才能继续执行下一步。 工作原理:每个线程在到达屏障点时调用wait(),当所有线程都调用wait()后,所有线程同时继续执行。 适用场景:需要所有线程完成特定阶段后才能进入下一阶段的任务

#include <iostream>
#include <thread>
#include <barrier>
#include <vector>
const int thread_count = 4;
std::barrier<> sync_point(thread_count);
void stage_worker(int id) {
std::cout << "线程 " << id << " 完成第一阶段" << std::endl;
sync_point.arrive_and_wait(); // 等待所有线程完成第一阶段
std::cout << "线程 " << id << " 开始第二阶段" << std::endl;
sync_point.arrive_and_wait(); // 等待所有线程完成第二阶段
std::cout << "线程 " << id << " 任务完成" << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < thread_count; ++i) {
threads.emplace_back(stage_worker, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}

🔄 读写锁(Read-Write Lock)

基本概念:读写锁区分读操作和写操作,允许多个读操作同时进行,但写操作必须互斥执行。 工作原理

  • 读锁:多个线程可以同时获取读锁
  • 写锁:只有一个线程可以获取写锁,且不能与读锁共存 适用场景:读多写少的场景,如缓存、配置文件访问
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <chrono>
std::shared_mutex rw_mutex;
int shared_data = 0;
void reader(int id) {
for (int i = 0; i < 5; ++i) {
{ // 临界区开始
std::shared_lock<std::shared_mutex> lock(rw_mutex); // 获取读锁
std::cout << "读者 " << id << " 读取数据: " << shared_data << std::endl;
} // 临界区结束
std::this_thread::sleep_for(std::chrono::milliseconds(50));
void writer(int id) {
for (int i = 0; i < 3; ++i) {
{ // 临界区开始
std::unique_lock<std::shared_mutex> lock(rw_mutex); // 获取写锁
shared_data++;
std::cout << "写者 " << id << " 更新数据为: " << shared_data << std::endl;
} // 临界区结束
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
int main() {
std::vector<std::thread> threads;
// 创建3个读者线程
for (int i = 0; i < 3; ++i) {
threads.emplace_back(reader, i);
}
// 创建2个写者线程
for (int i = 0; i < 2; ++i) {
threads.emplace_back(writer, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}

🔄 自旋锁(Spin Lock)

基本概念:自旋锁在获取锁失败时不会阻塞,而是不断循环检查锁是否可用,避免了上下文切换的开销。 工作原理:使用原子操作实现锁的获取和释放,线程在获取锁时自旋等待。 适用场景:临界区执行时间短、线程数量少的场景

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
class SpinLock {
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// 自旋等待
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
SpinLock spin_lock;
int shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
spin_lock.lock();
shared_data++;
spin_lock.unlock();
}
}
int main() {
std::vector<std::thread> threads;
// 创建4个线程
for (int i = 0; i < 4; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "共享数据最终值: " << shared_data << std::endl;
return 0;
}

⚛️ 原子操作(Atomic Operation)

基本概念:原子操作是一种无锁同步机制,在单个CPU指令中完成数据的读取和修改,不会被其他线程打断。 工作原理:使用硬件支持的原子指令,确保操作的原子性,无需使用锁。 适用场景:简单的计数器、标志位等,避免锁的开销

#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> shared_data = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
shared_data++;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "共享数据最终值: " << shared_data << std::endl;
return 0;
}

📊 同步方式对比

同步方式适用场景优点缺点
互斥锁保护临界区简单易用可能导致死锁、优先级反转
信号量控制并发数量灵活控制资源访问实现复杂,容易出错
条件变量线程间通信高效等待条件容易使用不当导致死锁
屏障多线程协调简化线程同步仅适用于固定数量线程
读写锁读多写少场景提高并发性能实现复杂
自旋锁短临界区避免上下文切换消耗CPU资源,可能导致饥饿
原子操作简单数据操作无锁,高性能仅适用于简单操作

🛠️ 最佳实践

  1. 优先使用原子操作:对于简单的数据操作,使用原子操作比锁更高效
  2. 选择合适的锁类型:根据场景选择读写锁、互斥锁或自旋锁
  3. 避免死锁:遵循锁的获取顺序,使用RAII管理锁的生命周期
  4. 减少临界区大小:临界区越小,并发性能越高
  5. 使用条件变量代替轮询:等待条件时使用条件变量,避免CPU资源浪费
  6. 考虑使用高级同步机制:如std::future、std::promise等

📋 总结

C++提供了多种线程同步机制,每种机制都有其适用场景和优缺点。在实际开发中,需要根据具体情况选择合适的同步方式:

  • 互斥锁:最通用的同步机制,适用于大多数场景
  • 信号量:控制资源访问数量
  • 条件变量:线程间通信和事件通知
  • 屏障:协调多线程执行顺序
  • 读写锁:优化读多写少场景的性能
  • 自旋锁:避免上下文切换开销
  • 原子操作:无锁同步,高性能 理解这些同步机制的原理和适用场景,是编写高效、安全的多线程程序的关键。

Thanks for reading!

15.多线程里线程的同步方式有哪些

2026-01-23
2321 字 · 12 分钟

已复制链接

评论区

目录