Hilos de Python y operaciones atómicas.

Quiero implementar un hilo con un método de stop() sincrónico.

He visto versiones como esta:

 class Thread1: def __init__(self): self._stop_event = threading.Event() self._thread = None def start(self): self._thread = threading.Thread(target=self._run) self._thread.start() def stop(self): self._stop_event.set() self._thread.join() def _run(self): while not self._stop_event.is_set(): self._work() def _work(self): print("working") 

Pero he leído que las operaciones atómicas son seguras para subprocesos y me parece que se puede hacer sin Event . Así que se me ocurrió esto:

 class Thread2: def __init__(self): self._working = False self._thread = None def start(self): self._working = True self._thread = threading.Thread(target=self._run) self._thread.start() def stop(self): self._working = False self._thread.join() def _run(self): while self._working: self._work() def _work(self): print("working") 

Piensa que una implementación similar se consideraría incorrecta en C, porque el comstackdor puede poner _working en un registro (o incluso optimizarlo) y el subproceso de trabajo nunca sabría que la variable ha cambiado. ¿Puede suceder algo así en Python? ¿Es correcta esta implementación? No pretendo evitar eventos o lockings por completo, solo quiero entender esto de las operaciones atómicas.

Por lo que puedo decir, también es incorrecto en Python, ya que _working todavía puede ser registrado u optimizado de alguna otra manera, o puede suceder alguna otra cosa que pueda cambiar su valor. Lee y escribe: este campo podría ser arbitrariamente reordenado por el procesador.

Bueno, digamos que en el mundo de multiproceso no deberías preguntar: por qué esto no debería funcionar , sino por qué esto está garantizado para trabajar .

Dicho esto, en la mayoría de los casos, el subprocesamiento múltiple es un poco más fácil en CPython, ya que GIL garantiza que:

  • Solo se ejecuta un comando de intérprete en un momento dado.
  • Las fuerzas a menudo sincronizan la memoria entre hilos.

Tenga en cuenta que GIL es un detalle de implementación, que podría desaparecer si alguien reescribe CPython sin él.

También tenga en cuenta que el hecho de que debería implementarse de esta manera en cualquier sistema real.

Aquí hay una solución más completa, que también se puede usar si el hilo del trabajador necesita retrasarse a veces.

 class Worker(threading.Thread): quit = False def __init__(self, ...): super().__init__() self.cond = threading.Condition() ... def delay(self, seconds): deadline = time.monotonic() + seconds with self.cond: if self.quit: raise SystemExit() if time.monotinic() >= deadline: return self.cond.wait(time.monotonic() - deadline) def run(self): while not self.quit: # work here ... # when delay is needed self.delay(123) def terminate(self): with self.cond: self.quit = True self.cond.notify_all() self.join() 

Y usado así:

 worker = Worker() worker.start() ... # finally worker.terminate() 

Por supuesto, si sabe a self.cond que el trabajador nunca duerme, puede eliminar la creación y todos los usos de self.cond , manteniendo el rest del código.