线程同步方式有哪些?

线程同步机制是指在多线程编程中,为了保证线程之间的互不干扰,而采用的一种机制。常见的线程同步机制有以下几种:

  1. 互斥锁:互斥锁是最常见的线程同步机制。它允许只有一个线程同时访问被保护的临界区(共享资源)
  2. 条件变量:条件变量用于线程间通信,允许一个线程等待某个条件满足,而其他线程可以发出信号通知等待线程。通常与互斥锁一起使用。
  3. 读写锁: 读写锁允许多个线程同时读取共享资源,但只允许一个线程写入资源。
  4. 信号量:用于控制多个线程对共享资源进行访问的工具。

1. 互斥锁 (Mutex - Mutual Exclusion)

原理:像一把钥匙,只允许一个线程进入被保护的代码段(临界区)。其他线程必须等待该线程释放锁。

类比:一个单人的卫生间,一个人进去后从里面锁门,其他人只能在门口等待。

示例(C++ std::mutex):

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 全局互斥锁
int shared_data = 0;

void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // 上锁:进入临界区
++shared_data; // 操作共享资源
mtx.unlock(); // 解锁:离开临界区
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Final value: " << shared_data << std::endl; // 总是 200000
return 0;
}

更安全的用法(std::lock_guard):

void increment_safe() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 构造时自动上锁,析构时自动解锁
++shared_data;
} // lock_guard 在此处超出作用域,自动解锁
}

2. 条件变量 (Condition Variable)

原理:允许线程在某个条件不满足时主动等待,直到其他线程改变条件并通知它。必须与互斥锁配合使用

类比:顾客在餐厅等空位。没有空位时(条件不满足),顾客就等待。服务员有空位时(改变条件),通知等待的顾客。

示例(C++ std::condition_variable):

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool is_ready = false; // 条件

void consumer() {
std::unique_lock<std::mutex> lock(mtx);
// 等待条件满足。如果条件不满足,则释放锁并休眠;被唤醒后重新检查条件。
cv.wait(lock, []{ return is_ready; });
std::cout << "Data is ready!\n";
}

void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备工作
{
std::lock_guard<std::mutex> lock(mtx);
is_ready = true; // 改变条件
} // 释放锁
cv.notify_one(); // 通知一个等待的线程
}

int main() {
std::thread t1(consumer);
std::thread t2(producer);

t1.join();
t2.join();
return 0;
}

3. 读写锁 (Read-Write Lock)

原理:允许多个线程同时读,但只允许一个线程写。写操作时,禁止所有读操作。

类比:公司的公告板。很多员工可以同时看(读),但只有行政人员可以修改内容(写),且修改时不允许其他人看。

示例(C++ std::shared_mutexC++17):

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex rw_mtx;
int shared_value = 0;

void reader(int id) {
std::shared_lock<std::shared_mutex> lock(rw_mtx); // 共享锁(读锁)
// 多个读者可以同时进入这里
std::cout << "Reader " << id << " sees value: " << shared_value << std::endl;
}

void writer(int id) {
std::unique_lock<std::shared_mutex> lock(rw_mutex); // 独占锁(写锁)
// 只有一个写者可以进入这里,且会阻塞所有读者和其他写者
++shared_value;
std::cout << "Writer " << id << " updated value to: " << shared_value << std::endl;
}

int main() {
std::thread readers[10];
std::thread writers[2];

for (int i = 0; i < 2; ++i) writers[i] = std::thread(writer, i);
for (int i = 0; i < 10; ++i) readers[i] = std::thread(reader, i);

for (auto& t : readers) t.join();
for (auto& t : writers) t.join();
return 0;
}

4. 信号量 (Semaphore)

原理:维护一个计数器,控制同时访问某个资源的线程数量。P操作(wait)减少计数,V操作(signal)增加计数。

类比:停车场门口的剩余车位计数器。车开进去(P操作),车位减一。车开出来(V操作),车位加一。计数器为0时,栏杆不放行。

注意:C++标准库没有直接提供信号量,但可以用互斥锁和条件变量实现,或者在C++20中使用 std::counting_semaphore

示例(概念性):

// 伪代码:用互斥锁和条件变量实现一个信号量
class Semaphore {
public:
Semaphore(int count) : count_(count) {}

void Wait() { // P操作
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return count_ > 0; }); // 等待计数器大于0
--count_;
}

void Signal() { // V操作
std::lock_guard<std::mutex> lock(mutex_);
++count_;
cv_.notify_one();
}

private:
int count_;
std::mutex mutex_;
std::condition_variable cv_;
};

// 使用:限制最多5个线程同时访问数据库连接池
Semaphore db_semaphore(5);

void access_database() {
db_semaphore.Wait(); // 获取一个许可
// ... 使用数据库连接
db_semaphore.Signal(); // 释放一个许可
}