有这样一种炸弹,它有一个控制中心,时不时发送消息给它;当它被引爆的时候,它将告诉控制中心, 并把自己从控制中心的联络列表中移除。在通常的代码中,由于接受消息或者引爆的时候,都会引起炸弹的状态变化,所以,我们会使用一个锁来保证这两个操作是有顺序的;另外,控制中心的控制列表也是会有锁来控制访问。下面是其中一个实现:
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(); }); } }
———————————————————————————————————————————————————————————————————————————————
炸弹问题的本质是,炸弹的事件引起控制中心的某个数据集合发生变化,而控制中心的线程会遍历集合,对炸弹进行操作。如果控制中心弱化成只是单纯的数据集合,那么就不属于炸弹问题。