死锁和scoped_lock

死锁

存在两种常见的死锁情况,第一种是对同一把锁连续加锁,结果第二次加锁时因为自己已经加了锁,会导致线程阻塞无法运行。

第二种是两个线程各自加了一把锁之后,还未解锁就去获取对方的锁。

线程t1加了锁A,t2加了锁B,两个线程都未解锁,然后t1去获取锁B,此时t1会阻塞;同样t2去获取锁A,也会阻塞。两个线程都阻塞也就无法解锁,程序无法再运行。此时可以将两个线程都杀死。为防止这种情况可以用trylock函数。

scoped_lock

boost::mutex::scoped_lock顾名思义就是在作用域内有效,当离开作用域自动释放锁,传递参数是锁。构造函数会进行上锁操作,析构函数会对互斥量进行解锁操作。这样当锁离开作用域时,析构函数会自动释放锁。 即使运行时抛出异常,由于析构函数仍然会运行,所以锁仍然能释放。

scoped_lock特别之处是,可以指定多个互斥量。

之前的mutex的那篇文章,把两个线程可以修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void thread_1(int n)
{
boost::mutex::scoped_lock lock_1(mut);
cout << "start thread 1" << endl;
sum = sum * n;
cout << "thread 1, sum: " << sum << endl;
}

void thread_2(int n)
{
boost::mutex::scoped_lock lock_2(mut);
cout << "start thread 2" << endl;
sum = sum * 7 * n;
cout << "thread 2, sum: " << sum << endl;
}

不用mutexlockunlock函数。

scoped_lock使用std::lock函数,其会调用一个特殊的算法对所提供的互斥量调用try_lock函数,这是为了避免死锁。

C++17引入的std::scoped_lock允许一次性锁住多个互斥量。可以传递多个互斥量给scoped_lock的构造函数,它会自动锁住所有传递的互斥量,并且在scoped_lock的生命周期结束时自动解锁,这样可以避免出现死锁,因为它会在一次性锁住所有互斥量时,自动避免死锁情况。

unique_lock在构造时只能锁住一个互斥量。但与scoped_lock不同的是,可以在后续的代码中手动解锁、重新锁住或者在不同的地方重新锁住另一个互斥量。这种灵活性有时可以用于更复杂的场景。