因为线程可以变为阻塞,且因为对象可以拥有互斥锁,这些锁能够阻止线程在锁被释放之前访问这个对象。所以就有可能出现这种情况,某个线程在等待另一个线程而第2个线程又在等待别的线程,以此类推,直到这个链上的最后一个线程回头等待第1个线程。这样就会得到一个由互相等待的线程构成的连续的循环,而使任何线程都不能运行——死锁。死锁的过程是很难重现的,调试的难点。
下面看一个经典的死锁问题——哲学家聚餐。
#include "stdafx.h" #include "zthread/Mutex.h" #include "zthread/CountedPtr.h" #include "zthread/Runnable.h" #include "zthread/Condition.h" #include "zthread/Guard.h" #include "Display.h" #include <iostream> #include <ctime> using namespace ZThread; using namespace std; //(一根)筷子 class Chopstick { public: Chopstick() : notTaken(Lock), taken(false){} //拿起筷子 void Take() { Guard<Mutex> g(Lock); //如果筷子此时已被拿起,再被拿起时,必须等待 while(taken == true) notTaken.wait(); taken = true; } //放下筷子 void Drop() { Guard<Mutex> g(Lock); taken = false; //唤醒要拿起它的线程 notTaken.signal(); } private: Mutex Lock; Condition notTaken; bool taken; }; class Philosopher : public Runnable { public: Philosopher(Chopstick& l, Chopstick& r, int idn, int ponder, CountedPtr<Display>& disp): left(l), right(r), id(idn), ponderFactor(ponder), display(disp) { } virtual void run() { try { while (!Thread::interrupted()) { output("Think!"); Thread::sleep(randSleepTime()); output("grabbing right"); right.Take(); output("grabbing left"); left.Take(); output("Eatting!"); Thread::sleep(randSleepTime()); right.Drop(); left.Drop(); } } catch (Interrupted_Exception& e) { cerr << " Jarry :" << e.what() <<endl; } } friend ostream& operator <<(ostream& os, const Philosopher& p) { return os << "Philosopher : " << p.id; } private: Chopstick& left; Chopstick& right; int id; int ponderFactor; CountedPtr<Display> display; int randSleepTime() { if (ponderFactor == 0) return 0; return rand() / (RAND_MAX / ponderFactor) * 250; } void output(string str) { ostringstream os; os << *this << " : " << str <<endl; display->OutPut(os); } }; int _tmain(int argc, _TCHAR* argv[]) { srand((unsigned int)time(0)); const int count = 5; int pender = 0; try { CountedPtr<Display> disp(new Display); ThreadedExecutor executor; Chopstick chopstick[count]; for (int i = 0; i < count; i ++) { //会出现死锁,是因为出现了循环等待 //executor.execute(new Philosopher(chopstick[i], chopstick[(i + 1) % count], i, pender, disp)); //打破发生死锁的第4个条件(循环等待) if (i < count - 1) executor.execute(new Philosopher(chopstick[i], chopstick[(i + 1)], i, pender, disp)); else executor.execute(new Philosopher(chopstick[0], chopstick[i], i, pender, disp)); } cin.get(); executor.interrupt(); executor.wait(); cin.get(); } catch (Synchronization_Exception& e) { cerr << " Jarry main " << e.what() <<endl; } return 0; }
如果在某一个时间点上所有的哲学家同时试图进餐,拿起同一侧的一根筷子,并且等待紧挨着他们的哲学家放下筷子,这样程序将会死锁。
同时满足以下4种条件,死锁就会发生:
1、相互排斥。线程使用的资源至少有一个必须是不可共享的。在这种情况下,一根筷子一次只能被一个哲学家使用。
2、至少有一个线程必须持有某一种资源,并且同时等待获得正在被另外的线程所持有的资源。也就是说要发生死锁一个哲学家必须持有一根筷子并且等待另一根筷子。
3、不能以抢占的方式剥夺一个线程的资源。所有线程只能把释放的资源作为一个正常事件。他们不会从别的哲学家手中抢夺筷子。
4、出一个循环等待。一个线程等待另外的线程所持有的资源,而这个等待的线程又等待另一个线程所持有的资源,以此类推直到某个线程去等待第一个线程所持有的资源。例子中每一个哲学家总是试图先得到右边的筷子,而后得到左边的筷子,所以发生了循环等待。
避免死锁只要打破以上四个条件中的一个就可以了!