发生死锁必须具备以下的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的原理就是这个