設計上の問題があります。複数のスレッドから一度にアクセスできないグローバル リソースがあるため、そのリソースへのアクセスをシリアル化するためにロックが必要です。ただし、Python のガベージ コレクターは__del__
、ロックを保持しながら何らかの処理を行っているときにメソッドを実行できます。デストラクタがリソースにアクセスしようとすると、デッドロックが発生します。
例として、次の無害に見えるシングルスレッド コードを考えてみましょう。これを実行するとデッドロックします。
import threading
class Handle(object):
def __init__(self):
self.handle = do_stuff("get")
def close(self):
h = self.handle
self.handle = None
if h is not None:
do_stuff("close %d" % h)
def __del__(self):
self.close()
_resource_lock = threading.Lock()
def do_stuff(what):
_resource_lock.acquire()
try:
# GC can be invoked here -> deadlock!
for j in range(20):
list()
return 1234
finally:
_resource_lock.release()
for j in range(1000):
xs = []
b = Handle()
xs.append(b)
xs.append(xs)
リソースは、同時に開いている複数の「ハンドル」を処理できます。そのため、それらのライフ サイクルを処理する必要があります。これをクラスに抽象化しHandle
、クリーンアップを配置すること__del__
は賢明な動きのように思えましたが、上記の問題はこれを破ります。
クリーンアップに対処する 1 つの方法は、ハンドルの「保留中のクリーンアップ」リストを保持すること__del__
です。実行時にロックが保持されている場合は、そこにハンドルを挿入し、後でリストをクリーンアップします。
質問は:
これをよりクリーンな方法で解決する
gc.disable()
/のスレッドセーフなバージョンはありますか?gc.enable()
これに対処する他のアイデアはありますか?