C++11中的同步互斥机制详解

C++
1.1k words

在并发编程中,多个线程共享资源时,互斥同步是避免数据竞争和保证数据一致性的关键手段。C++11引入了一系列强大的线程和同步库,包括互斥(mutex)和条件变量(condition_variable

本篇博客将详细介绍 C++11 中的互斥同步机制,重点包括 std::mutexstd::lock_guardstd::unique_lock 以及条件变量的使用场景和实现原理。


互斥锁:std::mutex

什么是 std::mutex

std::mutex 是 C++11 提供的最基本的互斥锁,用于保护共享资源,确保同一时刻只有一个线程能够访问该资源。它提供了 lock()unlock() 两个基本方法,分别用于加锁和解锁。

std::mutex 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 全局互斥锁
int counter = 0; // 全局共享资源

void increment() {
mtx.lock(); // 加锁
++counter; // 修改共享资源
mtx.unlock(); // 解锁
}

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

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

std::cout << "Final counter: " << counter << std::endl;
return 0;
}

在这个例子中,两个线程 t1t2 都在对 counter 进行自增操作。通过 std::mutex 进行加锁和解锁,确保了同一时刻只有一个线程能够访问和修改 counter,避免了数据竞争。

存在的问题

虽然 std::mutex 提供了基础的互斥功能,但手动调用 lock()unlock() 容易出错。例如,如果程序在 lock() 后发生异常或者忘记调用 unlock(),就可能导致死锁或资源长时间被占用。因此,C++11 提供了 std::lock_guardstd::unique_lock 来简化锁的管理。


std::lock_guard:自动管理锁的RAII机制

什么是 std::lock_guard

std::lock_guard 是 C++11 提供的 RAII(资源获取即初始化)机制的封装类,用于自动管理 mutex 的生命周期。构造时加锁,析构时解锁,确保在函数作用域结束时自动释放锁,避免了手动管理锁的复杂性。

使用 std::lock_guard

1
2
3
4
void increment() {
std::lock_guard<std::mutex> guard(mtx); // 自动加锁
++counter; // 修改共享资源
} // 离开作用域时自动解锁

在上面的代码中,当 guard 对象离开作用域时,mutex 会被自动解锁,即使发生异常也不会忘记解锁,从而大大提升了代码的健壮性。


std::unique_lock:更灵活的锁管理

什么是 std::unique_lock

std::unique_lock 是一个比 std::lock_guard 更加灵活的锁管理类。与 std::lock_guard 不同,std::unique_lock 支持:

  • 延迟锁定:可以在对象创建时不锁定,而在需要时调用 lock()
  • 提前解锁:可以手动调用 unlock() 解除锁定。
  • 通过 try_lock() 尝试加锁,而不是等待锁定。

std::unique_lock 的使用

1
2
3
4
5
6
void increment() {
std::unique_lock<std::mutex> ulock(mtx); // 加锁
++counter;
ulock.unlock(); // 提前解锁
// 其他非共享资源的操作
}

std::unique_lock 提供了更灵活的加锁和解锁控制,非常适合需要手动解锁或多次锁定的场景。尽管灵活性增加,但一般情况下仍推荐使用简单的 std::lock_guard,除非确实需要这种额外的灵活性。


条件变量:std::condition_variable

什么是 std::condition_variable

std::condition_variable 是一个同步机制,用于让一个线程等待另一个线程满足某个条件。条件变量通常与 std::unique_lock 一起使用,因为它要求有灵活的锁定和解锁操作。

条件变量的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待条件满足
std::cout << "Thread is running" << std::endl;
}

void signal() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知一个等待线程
}

int main() {
std::thread t1(worker);
std::thread t2(signal);

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

return 0;
}

在这个例子中,worker 线程在等待条件变量 cv,直到 ready 变为 truesignal 线程在修改 ready 后,通过 notify_one() 唤醒等待的线程。

wait 的工作原理

wait() 方法会先解锁传入的互斥锁,然后进入阻塞状态。当条件满足时,它会重新加锁并继续执行。条件变量常用于生产者-消费者模型或多线程任务调度。


其他C++11同步机制

除了 std::mutex 及其相关工具,C++11 还提供了其他几种常用的同步机制:

  • std::recursive_mutex:允许同一线程多次加锁,而不会导致死锁。
  • std::timed_mutex:支持超时的 mutex,可以使用 try_lock_fortry_lock_until 进行定时加锁。
  • std::shared_mutex:允许多个线程共享读锁定,适用于读多写少的场景。
Comments