炸弹问题——一种会引发死锁的情景模式

有这样一种炸弹,它有一个控制中心,时不时发送消息给它;当它被引爆的时候,它将告诉控制中心, 并把自己从控制中心的联络列表中移除。在通常的代码中,由于接受消息或者引爆的时候,都会引起炸弹的状态变化,所以,我们会使用一个锁来保证这两个操作是有顺序的;另外,控制中心的控制列表也是会有锁来控制访问。下面是其中一个实现:

https://github.com/AmongOthers/BombsProblem/commits/deadlock_version

运行过程中将会出现死锁的情况。原因是:

Bomb:
public void Fire() { log("Bomb.Fire: lock core begin"); lock (mCoreLock) { log("Bomb.Fire: lock core end"); mCenter.RemoveBomb(this); fire(); } }
BombCenter:    
   public void RemoveBomb(Bomb bomb) { log("BombCenter removeBomb: lock bombs begin"); lock (mBombsLock) { log("BombCenter removeBomb: lock bombs end"); mBombs.Remove(bomb); } }

上面的过程需要的锁的顺序是:Bomb.mCoreLock->BombCenter.mBombsLock

而控制中心呼叫炸弹的过程:

BombCenter:     
   public void callBombs() { log("BombCenter fireBombs: lock bombs begin"); lock (mBombsLock) { log("BombCenter fireBombs: lock bombs end"); foreach (var bomb in mBombs) { bomb.OnCommandReceived(); } } }
            
  Bomb:
       public void OnCommandReceived() { log("Bomb.onCommandReceived: lock core begin"); lock (mCoreLock) { log("Bomb.onCommandReceived: lock core end"); dumb(); } }

需要的锁的顺序是: BombCetner.mBombsLock->Bomb.mCoreLock

当这两个过程并发进行的话,就有可能会造成死锁的情况。本质上来说,这是"哲学家吃饭问题"的变种,但是它比较隐蔽,一般来说,会有一个消息的订阅者的基本模式,而订阅者会自我销毁,并主动要求消息中心从列表中移除自己。这样的套路用的还是蛮多的,但是有可能会产生死锁的问题。

一种解决方案是,炸弹不直接要求控制中心移除自己,而是设置自己一个“可以被废弃”的标志,而由控制中心在适当的时候移除。

https://github.com/AmongOthers/BombsProblem/commits/set_remove_flag_version

BombCenter:
public void callBombs() { log("BombCenter fireBombs: lock bombs begin"); lock (mBombsLock) { mBombs.RemoveAll((bomb) => { return bomb.IsFired; }); log("BombCenter fireBombs: lock bombs end"); foreach (var bomb in mBombs) { bomb.OnCommandReceived(); } } }

但是如果账单中心没有所谓的合适的地方放置移除标记了的炸弹(没有自己的线程),那么另外一种解决方案就是,使用"异步移除"。

https://github.com/AmongOthers/BombsProblem/tree/async_remove

Bomb:
            public void Fire()
            {
                log("Bomb.Fire: lock core begin");
                lock (mCoreLock)
                {
                    log("Bomb.Fire: lock core end");
                    ThreadPool.QueueUserWorkItem(delegate
                    {
                        mCenter.RemoveBomb(this);
                        fire();
                    });
                }
            }

———————————————————————————————————————————————————————————————————————————————

 炸弹问题的本质是,炸弹的事件引起控制中心的某个数据集合发生变化,而控制中心的线程会遍历集合,对炸弹进行操作。如果控制中心弱化成只是单纯的数据集合,那么就不属于炸弹问题。

原文地址:https://www.cnblogs.com/zhengwenwei/p/2755502.html