一、异常导致没有解锁
mutex对象需要手动解锁。但是如果在解锁之前抛出来异常,就会导致解锁逻辑没有执行。当前线程就会一直占有互斥量,其它线程就一直无法得到互斥量,就无法执行,看代码:
#include <iostream>
#include <thread>
#include <mutex>
#include <stdexcept>
std::mutex mtx;
void print_event(int x)
{
if(x%2==0)
std::cout << x << " is even\n";
else
throw (std::logic_error("not even"));
}
void print_thread_id(int id)
{
try{
mtx.lock();
print_event(id);
mtx.unlock();
}
catch(std::logic_error&)
{
std::cout << "[exception caught]\n";
}
}
int main(int argc,char **argv)
{
std::thread threads[10];
for(int i=0;i<10;i++)
{
threads[i]=std::thread(print_thread_id,i+1);
}
for (auto& th : threads) th.join();
return 0;
}
执行后会发现程序被卡主,因为发生了死锁。
二、基于RAII思想的unique_lock
unique_lock在构造的时候传入mutex变量,对mutex变量加锁。在析构的时候对这个mutex变量解锁,从而实现了自动解锁。print_thread_id函数的代码可以替换为::
void print_thread_id(int id)
{
try{
std::unique_lock<std::mutex> ul(mtx);
print_event(id);
}
catch(std::logic_error&)
{
std::cout << "[exception caught]\n";
}
}
输出结果:
[exception caught]
[exception caught]
2 is even
4 is even
[exception caught]
6 is even
[exception caught]
8 is even
[exception caught]
10 is even
死锁解除。
三、unique_lock和lock_guard的区别
1、unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。
2、unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁, lck.lock()进行上锁,而不必等到析构时自动解锁。lock_guard是不支持手动释放的。
3、一般来说,使用unique_lock比较多,除非追求极致的性能才会考虑使用lock_guard。