J'ai un problème de conception : il y a une ressource globale à laquelle on ne peut pas accéder depuis plusieurs threads à la fois, et j'ai donc besoin d'un verrou autour d'elle pour sérialiser l'accès à cette ressource. Cependant, le ramasseur d'ordures de Python peut exécuter une commande __del__
pendant que j'effectue un traitement tout en maintenant le verrou. Si le destructeur essaie d'accéder à la ressource, cela aboutit à un blocage.
À titre d'exemple, considérons le code monofilaire d'apparence innocente suivant, qui se bloque si vous l'exécutez :
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)
La ressource peut gérer plusieurs "handles" ouverts en même temps, et je dois gérer leur cycle de vie. En abstrayant cela dans un Handle
et de mettre le nettoyage dans __del__
semblait être un choix intelligent, mais le problème ci-dessus le brise.
Une façon de gérer le nettoyage est de garder une liste de handles "pending cleanup", et si le verrou est maintenu quand __del__
est exécuté, insérez la poignée à cet endroit, et nettoyez la liste plus tard.
La question est la suivante :
-
Existe-t-il une version threadsafe de
gc.disable()
/gc.enable()
qui résoudrait le problème de manière plus propre ? -
D'autres idées pour régler ce problème ?