在写c/c++的过程中遇到死锁常常会让我们手无足错,因为死锁往往不像其他类型的错误一样会在终端直接打印报错,因此难以被发现并需要大量精力去排错。是时候思考,该如何防范或者避免死锁。
死锁场景1
解决途径1
死锁场景2(如下图)
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd):some_detail(sd){}
friend void swap(X& lhs, X& rhs)
{
if(&lhs==&rhs)
return;
std::lock_guard<std::mutex> lock_a(lhs.m); // 2
std::lock_guard<std::mutex> lock_b(rhs.m); // 3
swap(lhs.some_detail,rhs.some_detail);
}
};
std::lock
——可以一次性锁住多个(两个以上)的互斥量,并且没有副作用(死锁风险)。std::lock
如果在上锁的过程中有一个锁是无法锁成功的,函数会unlock所有的锁。内部是用try_lock实现的。// 这里的std::lock()需要包含头文件
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd):some_detail(sd){}
friend void swap(X& lhs, X& rhs)
{
if(&lhs==&rhs)
return;
std::lock(lhs.m,rhs.m); // 1
std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock); // 2
std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock); // 3
swap(lhs.some_detail,rhs.some_detail);
}
};
class a_simple_vector {
public:
void add_to_vec(int a) {
std::lock_guard<std::mutex> lg(m);
if (!a_user_func(a))
v.push_back(a);
}
bool a_user_func(int a) {
std::lock_guard<std::mutex> lg(m);
for (auto &&i : v) {
if (i == a) {
printf("非法\n");
return -1;
}
}
return 0;
}
private:
std::mutex m;
vector<int> v;
};
class a_simple_vector {
public:
void add_to_vec(int a) {
std::lock_guard<std::mutex> lg(m);
a_user_func_need_lock(a);
v.push_back(a);
}
private:
bool a_user_func_need_lock(int a) {
for (auto &&i : v) {
if (i == a) {
printf("非法\n");
return -1;
}
}
return 0;
}
public:
bool a_user_func(int a) {
std::lock_guard<std::mutex> lg(m);
for (auto &&i : v) {
if (i == a) {
printf("非法\n");
return -1;
}
}
return 0;
}
private:
std::mutex m;
vector<int> v;
};
死锁场景4
解决途径4
std::lock()
,std::lock(b.prev.m,b.m)
的顺序请求锁,遍历的过程此时只允许从前向后,travel函数中如果拥有了A锁,此时del是不能拿到A锁的,或者del同时有了AB两锁,travel是拿不到A锁的。相同的请求顺序避免了死锁问题,在del拿到俩把锁之后再去请求C锁也就顺理成章了,当然即使其他线程拥有这个C,只要是接口统一从前向后锁定的del或者traval或者insert或者push_back,都是不会出现死锁等待的。(代码其实该写一下试一下)死锁场景5(如下图)
解决途径5
class Inventory
{
public:
void add(Request* req)
{
muduo::MutexLockGuard lock(mutex_);
requests_.insert(req);
}
void remove(Request* req) __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
requests_.erase(req);
}
void printAll() const;
private:
mutable muduo::MutexLock mutex_;
std::set<Request*> requests_;
};
Inventory g_inventory;
class Request
{
public:
void process() // __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
g_inventory.add(this);
// ...
}
~Request() __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
sleep(1);
g_inventory.remove(this);
}
void print() const __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
// ...
}
private:
mutable muduo::MutexLock mutex_;
};
```cpp
void Inventory::printAll() const
{
muduo::MutexLockGuard lock(mutex_);
sleep(1);
for (std::set<Request*>::const_iterator it = requests_.begin(); it != requests_.end(); ++it)
{
(*it)->print();
}
printf("Inventory::printAll() unlocked\n");
}
void threadFunc()
{
Request* req = new Request;
req->process();
delete req; //~Request()
}
int main()
{
muduo::Thread thread(threadFunc);
thread.start();
usleep(500 * 1000);
g_inventory.printAll();
thread.join();
}
解决方案
void Inventory::printAll() const
{
std::set<Requests*>requests;
{
muduo::MutexLockGuard lock(mutex_);
requests=requests_;
}
for (std::set<Request*>::const_iterator it = requests.begin(); it != requests.end(); ++it)
{
(*it)->print();
}
printf("Inventory::printAll() unlocked\n");
}
至于如何有顺序的加锁,在《c++编程实战》上就提供了层次锁的方案:通过thread_local来隔离各自线程中锁的默认权值,在每一次加锁都逐渐降低本线程的权值,并保证每一次的锁的权值都比上一次小,否则抛出异常的方式,如果我们误添加了比当前线程更高的权值,会先发生异常结束程序,而非把检查死锁的任务拖到发生死锁时再去一步步查验。
#include
#include
#include
class hierarchical_mutex {
std::mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned long previous_hierarchy_value;
static thread_local unsigned long this_thread_hierarchy_value;
void check_for_hierarchy_violation() {
if (this_thread_hierarchy_value <= hierarchy_value) {
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value() {
previous_hierarchy_value = this_thread_hierarchy_value;
this_thread_hierarchy_value = hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value) :
hierarchy_value(value),
previous_hierarchy_value(0) {}
void lock() {
check_for_hierarchy_violation();
internal_mutex.lock();
update_hierarchy_value();
}
void unlock() {
this_thread_hierarchy_value = previous_hierarchy_value;
internal_mutex.unlock();
}
bool try_lock() {
check_for_hierarchy_violation();
if (!internal_mutex.try_lock())
return false;
update_hierarchy_value();
return true;
}
};
thread_local unsigned long
hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);
int main() {
hierarchical_mutex m1(42);
hierarchical_mutex m2(2000);
}
慎用trylock,避免程序串行化(但可以使用它去避免一些死锁)
今天的死锁的就讲到这,小伙伴们三连哦!
《c++编程并发实战》
《Linux多线程服务器编程 》