线程死锁一引起的系列思考
发生死锁必须具备以下的4个条件
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
:资源是互斥的
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
:如果只请求一个资源 那么也不会发现
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
:所有权
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
:比如操作系统课老师举得例子 几个人吃饭,拿筷子问题 ,最后都会没拿到2只筷子而都吃不了饭
死锁例子
static std::recursive_mutex _mutex1; static std::recursive_mutex _mutex2; int hp; int main(int argc, char *argv[]) { thread t ( []() { _mutex1.lock(); Sleep(12); _mutex2.lock(); cout << "t:"<< hp << endl; _mutex1.unlock(); _mutex2.unlock(); }); t.detach(); _mutex2.lock(); Sleep(12); _mutex1.lock(); cout << "main:" << hp << endl; _mutex1.unlock(); _mutex2.unlock(); system("pause"); return 0; }
mutex1 mutex2 为2个资源,造成了环路等待条件 导致线程死锁
递归问题:递归加锁了 如
void doWith() { _mutex1.lock(); cout << "doWith:" << hp << endl; _mutex1.unlock(); } int main(int argc, char *argv[]) { thread t ( [=]() { _mutex1.lock(); cout << "t:"<< hp << endl; doWith(); _mutex1.unlock(); }); t.detach(); system("pause"); return 0; }
mutex被递归加锁了 而 std::mutex 非递归锁,子程序和 匿名函数 位于同一个线程空间 就又会造成 死锁,因为子程序在等待 匿名函数的解锁,而进入死锁
这时候可用 std::recursive_mutex 递归锁 替代std::mutex 或者自己实现一个递归锁
原理大致是 如果 和上次成功lock的操作的线程 不在同一线程 就真正的 lock 否则仅仅是引用计数加1
以下是我的一个实现
class Re_mutex { public: void lock() { cout << "lock id "<<this_thread::get_id() << endl; if (this_thread::get_id() == _last_lock_id &&_islock == true) { ++count; } else { _last_lock_id = this_thread::get_id(); _islock = true; _mutex.lock(); } } void unlock() { --count; if (count == 0 && _islock) { _mutex.unlock(); } } private: bool _islock = false; mutex _mutex; int count = 0; thread::id _last_lock_id; }; static Re_mutex _mutex1; //static std::recursive_mutex _mutex2; int hp; void doWith() { _mutex1.lock(); cout << "doWith:" << hp << endl; _mutex1.unlock(); } int main(int argc, char *argv[]) { thread t ( [=]() { _mutex1.lock(); cout << "t:"<< hp << endl; doWith(); _mutex1.unlock(); }); t.detach(); system("pause"); return 0; }
解决死锁的代价比较大,所以一般解决办法是 通过良好的设计 避免 死锁
标准库提供了一套 避免死锁的方案
std::lock 该锁 把需要访问的资源 要么都加锁 要么都不加锁 来避免死锁
template<class _Lock0, class _Lock1, class... _LockN> inline void lock(_Lock0& _Lk0, _Lock1& _Lk1, _LockN&... _LkN) { // lock N mutexes int _Res = 0; while (_Res != -1) _Res = _Try_lock(_Lk0, _Lk1, _LkN...); }
lock_guard可以通过调用重载构造函数 来避免加锁 ,来RAII资源
std::lock_guard<std::mutex> l(m,std::adopt_lock);
避免死锁的一些方法
避免嵌套锁 一个线程已经获得了一个锁的同时 别去获取第二个 如果需要 那就用std::lock 来获取 避免在持有锁的情况下调用用户代码,因为你不知道永不要做什么 使用固定顺序获取锁,如获取了A才能获取B,这样就不会导致循环等待的情况, 当然std::lock的原理就是这个