多线程死锁及解决办法
死鎖是由于不同線(xiàn)程按照不同順序進(jìn)行加鎖而造成的。如:
線(xiàn)程A:對(duì)lock a加鎖?=>?對(duì)lock b加鎖?=> dosth =>?釋放lock b =>?釋放lock a
線(xiàn)程B:對(duì)lock b加鎖?=>?對(duì)lock a加鎖?=> dosth =>?釋放lock a =>?釋放lock b
這樣兩條線(xiàn)程,就可能發(fā)生死鎖問(wèn)題。要避免發(fā)生死鎖,應(yīng)該使用同一個(gè)順序進(jìn)行加鎖。
這點(diǎn)在對(duì)象單向調(diào)用的情況下是很容易達(dá)成的。對(duì)象單向調(diào)用的意思是如果對(duì)象a的函數(shù)調(diào)用了對(duì)象b的函數(shù),則對(duì)象b中的函數(shù)不會(huì)去調(diào)用對(duì)象a的函數(shù)(注意:a和b也可能同屬于一個(gè)類(lèi))。
舉個(gè)例子吧,假設(shè)聊天室(Room)對(duì)象room,聊天者(Chatter)對(duì)象chatter,假設(shè)Chatter和Room的定義如下:
class InnerChatter
{
public:
???????void sendMsg(const string& msg)
???????{
??????????????boost::mutex::scoped_lock lock(mtx);
??????????????socket->send(msg);
}
private:
???????boost::mutex mtx;
???????TcpSocket socket;
};
typedef boost::shared_ptr< InnerChatter> Chatter;
?
class InnerRoom
{
public:
???????void sendMsg(const string& user, const string& msg)
???????{
??????????????boost::mutex::scoped_lock lock(mtx);
??????????????if (chatters.find(user) != chatters.end())
??????????????{
?????????????????????chatters[user]-> sendMsg(user);
??????????????}
???????}
private:
???????boost::mutex mtx;
???????map<string, Chatter> chatters;
};
?
目前代碼中只存在Room調(diào)用Chatter的情況,不存在Chatter調(diào)用Room,Room調(diào)用Room,Chatter調(diào)用Chatter這三種情況。所以總是先獲得room鎖,再獲得chatter鎖,不會(huì)發(fā)生死鎖。
如果為Chatter加上發(fā)送歷史和以下這個(gè)方法之后呢?
vector<string> history;
void sendMsgToChatter(Chatter dst, const string& msg)
{
???????boost::mutex::scoped_lock lock(mtx);???//?加鎖當(dāng)前對(duì)象
???????history.push_back(msg);
???????dsg>sendMsg(msg);??????//?注意:次函數(shù)調(diào)用會(huì)加鎖dst對(duì)象
}
乍看起來(lái)似乎沒(méi)問(wèn)題,但如果線(xiàn)程A執(zhí)行chatterA.sendMsgToChatter(chatterB, “sth”)時(shí),線(xiàn)程B正好執(zhí)行chatterB.sendMsgToChatter(chatterA, “sth”),就會(huì)發(fā)生本文一開(kāi)頭舉例的死鎖問(wèn)題。
如果在Chatter中加入函數(shù):
void sendMsgToAll(Room room, const string& msg)
{
???????boost::mutex::scoped_lock lock(mtx);
???????history.push_back(msg);
???????room->sendMsgToAll(msg);
}
在Room中加入函數(shù):
void sendMsgToAll(const string& msg)
{
???????boost::mutex::scoped_lock lock(mtx);
???????for (map<string, Chatter>::iterator it = chatters.begin(); it != chatters.end(); ++it)
???????{
??????????????it->second->sendMsg(msg);
???????}
}
顯然死鎖問(wèn)題更嚴(yán)重了,也更令人抓狂了。也許有人要問(wèn),為什么要這么做,不能就保持Room單向調(diào)用Chatter嗎?大部分時(shí)候答案是肯定的,也建議大部分模塊尤其是周邊模塊如基礎(chǔ)設(shè)施模塊使用明確清晰的單向調(diào)用關(guān)系,這樣可以減少對(duì)死鎖的憂(yōu)慮,少白一些頭發(fā)。
但有時(shí)候保證單向調(diào)用的代價(jià)太高:試想一下,如果被調(diào)用者b是一個(gè)容器類(lèi),調(diào)用者a定義了一些對(duì)元素的匯總操作如求和,為了避免回調(diào)(回調(diào)打破了單向調(diào)用約束),那就只有對(duì)b加鎖,復(fù)制所有元素,解鎖,遍歷求和。復(fù)制所有元素比較耗計(jì)算資源,有可能成為性能瓶頸。
另外還有設(shè)計(jì)方面的考慮。還舉Room和Chatter的例子,如果避免Chatter調(diào)用Room和Chatter,則Chatter很難實(shí)現(xiàn)啥高級(jí)功能,這樣所有代碼都將堆砌在Room,Room將成為一個(gè)超級(jí)類(lèi),帶來(lái)維護(hù)上的難度。此外還有設(shè)計(jì)上的不妥:因?yàn)閹缀跞棵嫦驅(qū)ο蟮脑O(shè)計(jì)模式都可以理解成某種方式的回調(diào),禁止回調(diào)也就禁掉了設(shè)計(jì)模式,可能帶來(lái)不便。
當(dāng)對(duì)象間的相互調(diào)用無(wú)法避免時(shí),如果只使用傳統(tǒng)的mutex,保證相同順序加鎖需要十分小心,萬(wàn)一編程時(shí)失誤,測(cè)試時(shí)又沒(méi)發(fā)現(xiàn)(這是很可能的,死鎖很不容易測(cè)試出來(lái)),如果條件允許還可以手忙腳亂地火線(xiàn)gdb,若無(wú)法調(diào)試定位,則服務(wù)器可能要成為重啟帝了,對(duì)產(chǎn)品的形象十分有害。
我想出的解決方案是既然mutex要保證相同順序加鎖,就直接讓mutex和一個(gè)優(yōu)先級(jí)掛鉤,使用線(xiàn)程專(zhuān)有存儲(chǔ)(TSS)保存當(dāng)前線(xiàn)程優(yōu)先級(jí)最低的鎖,當(dāng)對(duì)新的mutex加鎖時(shí),如果mutex的優(yōu)先級(jí)<?當(dāng)前優(yōu)先級(jí)(為什么=不可以,參考上文說(shuō)的sendMsgToChatter函數(shù)),才允許加鎖,否則記錄當(dāng)前函數(shù)棧信息,拋出異常(要仔細(xì)設(shè)計(jì)以免破壞內(nèi)部數(shù)據(jù)結(jié)構(gòu))。代碼如下:
?
boost::thread_specific_ptr<global::stack<int>> locks_hold_by_current_thread;
?
class xrecursive_mutex
{
public:???????????
???????xrecursive_mutex(int pri_level_)
??????????????: recursion_count(0)
??????????????, pri_level(pri_level_){}
???????~xrecursive_mutex(){}????????
?
???????class scoped_lock
???????{
???????public:
??????????????scoped_lock(xrecursive_mutex& mtx_)
?????????????????????: mtx(mtx_)
??????????????{
?????????????????????mtx.lock();
??????????????}
??????????????~scoped_lock()
??????????????{
?????????????????????mtx.unlock();
??????????????}
???????private:??????????
??????????????xrecursive_mutex& mtx;
???????};
??????
private:
???????int recursion_count;
???????int pri_level;
???????boost::recursive_mutex mutex;
?
???????int get_recursion_count()
???????{??????????????????
??????????????return recursion_count;
???????}
?
???????void lock()
???????{
??????????????mutex.lock();
??????????????++ recursion_count;???????????????????
??????????????if (recursion_count == 1)
??????????????{
?????????????????????if (locks_hold_by_current_thread.get() == NULL)
?????????????????????{
????????????????????????????locks_hold_by_current_thread.reset(new std::stack<int>());???????????????????????????
?????????????????????}
?????????????????????if (!locks_hold_by_current_thread->empty() &&
????????????????????????????locks_hold_by_current_thread->top()>= pri_level)
?????????????????????{?????//?????wrong order, lock failed
????????????????????????????-- recursion_count;
????????????????????????????mutex.unlock();????
????????????????????????????XASSERT(false);//記錄棧信息,拋異常
?????????????????????}
?????????????????????locks_hold_by_current_thread->push(pri_level);
??????????????}??????????????????
???????}
??????
???????void unlock()
???????{
??????????????bool bad_usage_flag = false;
??????????????if (recursion_count == 1 &&locks_hold_by_current_thread.get() != NULL)
??????????????{
?????????????????????if (!locks_hold_by_current_thread->empty()
????????????????????????????&& (locks_hold_by_current_thread->top() == pri_level))
?????????????????????{?????????????????????????
????????????????????????????locks_hold_by_current_thread->pop();
?????????????????????}????
?????????????????????else
?????????????????????{
????????????????????????????bad_usage_flag = true;
?????????????????????}
??????????????}
??????????????-- recursion_count;
??????????????mutex.unlock();????
??????????????XASSERT(!bad_usage_flag);//??????//?記錄棧信息,拋異常
???????}
?
};
?
使用:
xrecursive_mutex mtx1(1);
xrecursive_mutex mtx2(2);
xrecursive_mutex mtx3(3);
xrecursive_mutex mtx3_2(3);
{
???????xrecursive_mutex::scoped_lock lock1(mtx1);??????// pass,?當(dāng)前線(xiàn)程鎖優(yōu)先級(jí)1
???????xrecursive_mutex::scoped_lock lock2(mtx3);??????// pass,?當(dāng)前線(xiàn)程鎖優(yōu)先級(jí)3
???????ASSERT_ANY_THROW(xrecursive_mutex::scoped_lock lock2_2(mtx3_2)); //?捕獲異常,因?yàn)閮?yōu)先級(jí)3 <=?當(dāng)前線(xiàn)程鎖優(yōu)先級(jí)
???????xrecursive_mutex::scoped_lock lock3(mtx3);??????// pass,?可重入鎖
???????xrecursive_mutex::scoped_lock lock4(mtx1);??????// pass,?可重入鎖
???????ASSERT_ANY_THROW(xrecursive_mutex::scoped_lock lock5(mtx2)); //?捕獲異常,因?yàn)閮?yōu)先級(jí)?2<=?當(dāng)前線(xiàn)程鎖優(yōu)先級(jí)3
}
來(lái)源:http://blog.sina.com.cn/s/blog_48d4cf2d0100mx4n.html
總結(jié)
以上是生活随笔為你收集整理的多线程死锁及解决办法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: hash()函数的实现
- 下一篇: boost库学习入门篇