对于多线程程序,我已经习惯于使用
原子对象模式(http://blog.youkuaiyun.com/skyMountain/archive/2006/10/04/1320443.aspx)来降低死锁的风险。
但最近一个项目中,使用了原子对象封装了所有多线程访问的资源,系统竟然死锁了,而且锁住的地方恰好是原子对象内部。经过思考,发现此前我们对原子模式的理解有误,以为只要每个对象的公开接口是线程安全的,就可以算是原子对象了,其实还有一个条件:在公开接口中,不能锁定其它公共资源。它可以使用其它原子对象,但使用前不能加锁。不能锁定公共资源,只能锁定内部资源:否则就不是原子对象了。
因此:
1、一般来说,应当想办法禁止原子对象之间的相互调用。
2、如果无法避免1,应当保证原子对象内部资源在锁住的时候不调用其它原子对象的接口。
这一点往往不容易做到,有的时候甚至我们不得不为此付出一定的代价。
3、如果无法避免2,那么应当保证原子对象之间的调用图是一棵单向的树,而不能是一张有回路的网。
只要避开了循环调用,这个系统仍然还是线程安全、无死锁的。
4、原子对象一个很容易被遗忘加锁的地方是析构函数。
原子对象正在删除的时候,其公开函数被其它线程调用了,那么系统很容易崩溃。必须仔细设计避免这一点。
但最近一个项目中,使用了原子对象封装了所有多线程访问的资源,系统竟然死锁了,而且锁住的地方恰好是原子对象内部。经过思考,发现此前我们对原子模式的理解有误,以为只要每个对象的公开接口是线程安全的,就可以算是原子对象了,其实还有一个条件:在公开接口中,不能锁定其它公共资源。它可以使用其它原子对象,但使用前不能加锁。不能锁定公共资源,只能锁定内部资源:否则就不是原子对象了。
因此:
1、一般来说,应当想办法禁止原子对象之间的相互调用。
2、如果无法避免1,应当保证原子对象内部资源在锁住的时候不调用其它原子对象的接口。
这一点往往不容易做到,有的时候甚至我们不得不为此付出一定的代价。
3、如果无法避免2,那么应当保证原子对象之间的调用图是一棵单向的树,而不能是一张有回路的网。
只要避开了循环调用,这个系统仍然还是线程安全、无死锁的。
4、原子对象一个很容易被遗忘加锁的地方是析构函数。
原子对象正在删除的时候,其公开函数被其它线程调用了,那么系统很容易崩溃。必须仔细设计避免这一点。