并发程序中如何规避死锁

      通常意义上的死锁是由于不同线程(也可能是进程或者虚拟线程)请求锁的顺序不同造成的。google如何避免死锁,随便一篇文章都会告诉你只要按照同样的顺序请求锁,就可以避免死锁。这可能适用于大部分并发应用。但有些并发程序情况比较复杂,很难应用该条规则。
      具体一点,举IM软件为例:假设每个用户(属性有用户名、连接啊、发送的消息列表、接收的消息列表之类)是类Client的实例client。假设每个client有个方法void sendMsg(Client& target, const Msg& msg),这个方法先把消息加入对方的接收列表,再把消息加入自己的发送列表。显然,第一步需要锁住对方对象,第二步需要锁住自己对象。那如果有两个线程:一个执行clienta.sentMsg(clientb, xxx),一个执行clientb.sendMsg(clienta, yyy)就将可能死锁。因为前者请求顺序是(clientb.lock=>clienta.lock),后者是(clienta.lock=>clientb.lock)。注意,将两个步骤顺序颠倒结果也是一样。
      应用万能定律?好吧。我们把所有的client的锁排好序,在每个sendMsg的开头按顺序请求所有需要的锁。OK?即使不在乎锁排序的时空开销和编程麻烦,还必须满足一个很严苛的要求:在sendMsg中不能去调用其他可能上锁的函数。否则假设sendMsg请求了锁11,锁40,其调用的函数请求了锁32,锁48。则一样违背了规则,仍有死锁可能。
      针对这个情况,可能会有一个想法,何必这么麻烦呢?用一个锁保护发送队列,一个锁保护接收队列。则前者请求顺序(clientb.recvq_lock=>clienta.sendq_lock),后者是(clienta.recvq_lock=>clienta.sendq_lock)。哇,避免死锁的同时提高了效率。但这只是一个看似美好的解决方案。根据我的经验,锁越多越可能死锁!尤其当程序足够复杂的时候。我的建议是如果不是成为性能瓶颈,尽量不要进行锁拆分。(今天我刚阅读出项目中某个类存在死锁可能,正是由于锁拆分引起的。)如果保证并发对象一个对象只用一个锁就可以减少很多死锁了。不怕不优化,就怕乱优化。
      如果是我会这么处理:第一步结束后就释放对方的锁。则整个函数执行中最多保有1个锁,自然不会有死锁问题。(估计有人会大喊:骗子,还以为要说啥高深的内容)。嗯,请参考我前几天的blog《一个笑话和其对开发的启示》。其实就是这么简单,避免同时握有多个锁,自然不会有死锁问题哈哈。具体如何应用?很简单,就两条规则。规则一:类的每个成员函数,在调用其他函数(包括本类的其他成员函数)前先释放本对象的锁(如果持有的话),在返回后再获得本对象的锁(如果需要的话); 规则二:在成员函数内部不去获得其他锁(需要获得其他对象的锁一般是要改目标对象的一些属性,把加锁+修改抽取成类的成员函数即可)。
      伪代码如下:     
void Client::addToRecvQueue(const Msg& msg)
{
      scoped_lock lk(lock); //RAII lock
      // TODO: add to recv queue
}
void Client::sendMsg(Client& target, const Msg& msg)
{
      target.addToRecvQueue(msg);
      {
            scoped_lock lk(lock); //RAII lock
            // TODO: add to send queue
      }
}
      如果所有类实现时都满足这两条规则,则不管他们之间调用关系如何错综复杂,都可以保证线程执行时总是只持有一个锁,自然也就没有死锁问题!
      哇哈哈哈,问题完美解决?从此一身轻松,远离死锁?没这么容易哈。首先有些时候这么做可能会有效率问题(长调用时加解锁太多次),另外有时候规则一是不可行的(规则二总是可行,而且建议始终采用,因为比较符合面向对象的思想)。比如:有一个类叫Room,是client的容器,有一个方法void sendToAllOtherClients(Client& source, const Msg& msg)。在遍历client的过程中必然要锁住Room对象,不然没法保证遍历的有效性。
      当容器对象纷纷粉墨登场后,情况变得更为复杂了。这时候需要把万能规则也用进来。即保证先锁容器锁,再锁其他对象锁。一个操作需要锁多个容器?呵呵。目前我还没遇到这种情况,遇到了再说吧。
     
     


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值