一:std::lock 介绍
std::lock
是 C++11 引入的一个函数,它可以原子性地锁定多个互斥量(mutex),确保在多个线程竞争锁时避免死锁。它的主要用途是在多个互斥量之间避免因不同线程的不同锁定顺序而导致的死锁问题。
二: std::lock 例子
下面是一个例子,演示了如何在多个线程之间原子性的锁定多个互斥量,避免死锁的情况发生:
#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>
// 定义一个结构体,包含一个互斥量,用于保护共享资源
struct CriticalData
{
std::mutex mut; // 互斥量,用于保护资源
};
// 模拟死锁的函数
void deadLock(CriticalData& a, CriticalData& b)
{
// 锁定第一个互斥量
std::unique_lock<std::mutex> guard1(a.mut);
std::cout << "Thread: " << std::this_thread::get_id() << " locking the first mutex" << '\n';
// 睡眠1毫秒,模拟一些操作
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// 锁定第二个互斥量,这里会出现死锁问题,因为另外一个线程可能已经锁定了第二个互斥量
std::unique_lock<std::mutex> guard2(b.mut);
std::cout << "Thread: " << std::this_thread::get_id() << " locking the second mutex" << '\n';
}
// 使用 std::lock 避免死锁的函数
void NoDeadLock(CriticalData& a, CriticalData& b)
{
// 使用 std::lock 原子性地锁定两个互斥量
std::lock(a.mut, b.mut); // 确保以相同的顺序锁定两个互斥量,避免死锁
std::cout << "Thread: " << std::this_thread::get_id() << " locking them both atomically" << '\n';
// 使用 std::lock_guard 管理锁的生命周期,std::adopt_lock 表示锁已经被 std::lock 锁定
std::lock_guard<std::mutex> lock1(a.mut, std::adopt_lock);
std::lock_guard<std::mutex> lock2(b.mut, std::adopt_lock);
}
int main() {
std::cout << '\n';
// 创建两个共享数据,分别包含互斥量
CriticalData c1;
CriticalData c2;
// 模拟死锁的线程:互斥量锁定顺序可能导致死锁
//std::thread t1([&] {deadLock(c1, c2); });
//std::thread t2([&] {deadLock(c2, c1); });
// 使用 std::lock 避免死锁的线程:保证两个互斥量原子性锁定
std::thread t1([&] {NoDeadLock(c1, c2); });
std::thread t2([&] {NoDeadLock(c2, c1); });
// 等待线程执行完成
t1.join();
t2.join();
// 如果输出这句话,表示没有发生死锁
std::cout << "If you print this sentence, it means there is no deadlock\n" << std::endl;
std::cout << '\n';
}
三:std::lock 的工作原理
std::lock
是一个基于"分布式锁"的算法,它通过将所有传入的互斥锁排序并使用"试探性"锁定的方法来避免死锁。其原理通常是:
- 获取锁的顺序:线程在请求锁时会对所有锁进行排序(通常是按互斥量的内存地址排序)。这样就能确保所有线程按照相同的顺序尝试锁定互斥量。
- 试探性锁定:线程首先尝试获取所有互斥锁。如果某个锁无法立即获得,所有其他锁会被释放,线程会等待一段时间后重新尝试。
- 原子性锁定:一旦所有锁都成功获得,线程将继续执行。通过原子性地锁定所有传入的互斥量,
std::lock
可以避免死锁。
下面是linux下的一种可能实现:
cpp
template <typename Mutex1, typename Mutex2, typename... Mutexes>
void lock(Mutex1& m1, Mutex2& m2, Mutexes&... m) {
// 创建一个锁的数组,所有传入的互斥量都会被传递给 lock 函数
// 对互斥量排序
std::array<Mutex*, sizeof...(m) + 2> mutexes = {&m1, &m2, &m...};
// 排序互斥量
std::sort(mutexes.begin(), mutexes.end());
// 尝试锁定所有互斥量
while (true) {
// 尝试获取每个锁
bool success = true;
for (auto& m : mutexes) {
if (pthread_mutex_trylock(m) != 0) {
success = false;
break;
}
}
if (success) {
return; // 如果成功锁定所有互斥量,退出函数
}
// 如果锁定失败,释放已锁定的互斥量并重试
for (auto& m : mutexes) {
pthread_mutex_unlock(m);
}
// 等待一段时间,防止忙等待
std::this_thread::yield();
}
}