std::recursive_mutex可以在同一个线程中嵌套使用。介绍了 lock_guard 和 mutex 是什么关系, lock_guard 和 unique_lock 的区别

std::recursive_mutex是一种递归锁,它允许同一线程多次加锁而不会导致死锁。在使用普通的std::mutex时,同一线程如果多次尝试加锁会造成死锁;而std::recursive_mutex则不会。它会跟踪当前线程的加锁次数,并在解锁时,只有当解锁次数与加锁次数相同时,锁才会真正释放。

以下是一个简单的示例,展示了如何在同一线程中嵌套使用std::recursive_mutex

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

std::recursive_mutex mtx;

void recursiveFunction(int count) {
if (count <= 0) return;

mtx.lock();
std::cout << "Lock acquired, count = " << count << std::endl;

// 递归调用
recursiveFunction(count - 1);

std::cout << "Releasing lock, count = " << count << std::endl;
mtx.unlock();
}

int main() {
std::thread t(recursiveFunction, 3);
t.join();
return 0;
}

在这个例子中,recursiveFunction函数递归调用自己,每次调用时都会加锁,直到count为零时停止递归。由于使用了std::recursive_mutex,即使在同一线程中多次加锁,也不会发生死锁。

lock_guard 和 mutex 是什么关系

std::lock_guardstd::mutex在 C++中是协同使用的关系。std::lock_guard是一个用于管理std::mutex(或其他锁类型)的 RAII(资源获取即初始化)机制,旨在更安全和便捷地管理互斥锁的加锁和解锁过程。

它们的关系

  • **std::mutex**:是 C++标准库中提供的基础互斥锁类,用于在线程间保护共享资源,避免数据竞争。通过显式调用lock()unlock()函数加锁和解锁来控制对共享资源的访问。
  • **std::lock_guard**:是一个模板类,用于自动管理std::mutex的加锁和解锁操作。它在构造时自动对传入的std::mutex对象加锁,在销毁时(通常是作用域结束时)自动解锁。因此,使用std::lock_guard可以确保即使在异常或函数提前返回时,锁也会被正确释放。

为什么要使用std::lock_guard

如果手动控制std::mutex的加锁和解锁,很容易因漏掉解锁操作或在出现异常时未释放锁而导致死锁。std::lock_guard简化了这一过程,通过在其生命周期内自动加锁和解锁,确保了代码的安全性和可读性。

示例代码

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

std::mutex mtx;

void safeFunction() {
// 使用 lock_guard 管理 mtx 的加锁和解锁
std::lock_guard<std::mutex> lock(mtx);

// 临界区,安全访问共享资源
std::cout << "Thread " << std::this_thread::get_id() << " is in critical section." << std::endl;

// lock 离开作用域后会自动解锁
}

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

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

在上面的例子中,std::lock_guard构造时会锁住mtx,在safeFunction结束时,std::lock_guard自动析构并解锁,从而避免了手动解锁可能带来的问题。

总结

std::lock_guardstd::mutex是合作关系:std::mutex是互斥锁的实现,而std::lock_guard是用于自动管理std::mutex加锁和解锁的工具。

lock_guard 和 unique_lock 的区别

std::lock_guardstd::unique_lock都是用于管理std::mutex的 RAII 类型锁,但它们在功能和灵活性上有一些区别。下面详细解释它们的差异。

1. 锁的灵活性

  • **std::lock_guard**:std::lock_guard非常简单,在构造时自动加锁,并且在析构时自动解锁。它没有提供其他任何锁管理的接口,因此无法手动解锁后再重新加锁。
  • **std::unique_lock**:std::unique_lock更灵活,它提供了加锁、解锁和重锁的功能。你可以在代码中选择性地调用lock()unlock()来手动控制锁的状态。

2. 延迟加锁和解锁

  • **std::lock_guard**:在创建std::lock_guard对象时必须立即加锁,没有延迟加锁的选项。
  • **std::unique_lock**:支持延迟加锁,即可以在构造时不加锁,稍后在需要的时候调用lock()来加锁。可以使用std::defer_lock标志来延迟加锁。

3. 条件变量的支持

  • **std::lock_guard**:无法与std::condition_variable配合使用,因为条件变量要求能够临时解锁和重新加锁,这需要更灵活的锁管理。
  • **std::unique_lock**:可以与std::condition_variable配合使用。在等待条件变量时,std::unique_lock可以暂时解锁互斥锁,以便其他线程获得锁,并在条件满足后重新加锁。

4. 开销

  • **std::lock_guard**:轻量级,没有额外的状态信息,因为它一旦加锁,就保持锁定状态直到销毁。适用于简单的场景。
  • **std::unique_lock**:稍微重一些,因为它维护了更多的状态信息(比如是否锁定、是否延迟等),以便支持更多功能。适用于需要灵活控制锁的场景。

使用示例

std::lock_guard

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <mutex>

std::mutex mtx;

void safeFunction() {
std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁
// 临界区
std::cout << "Thread " << std::this_thread::get_id() << " is in critical section.\n";
} // 作用域结束,自动解锁

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
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>

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

void waitingThread() {
std::unique_lock<std::mutex> lock(mtx); // 构造时加锁
cv.wait(lock, [] { return ready; }); // 等待条件变量,期间自动解锁并在条件满足后重新加锁
std::cout << "Thread " << std::this_thread::get_id() << " finished waiting.\n";
}

void setReady() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all(); // 唤醒所有等待的线程
}

int main() {
std::thread t1(waitingThread);
std::thread t2(setReady);

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

总结

特性 std::lock_guard std::unique_lock
加锁和解锁灵活性 固定,只能自动加锁和解锁 灵活,支持手动加锁、解锁、重新加锁
延迟加锁 不支持 支持,通过std::defer_lock指定
条件变量支持 不支持 支持
适用场景 简单加锁、解锁 需要更多锁管理控制的复杂场景
性能 更轻量级 较重,带有额外状态信息

总体而言,如果只需要简单的加锁解锁,std::lock_guard更合适;如果需要更灵活的锁控制(如条件变量、延迟加锁等),则std::unique_lock是更好的选择。