2009-01-15 14 views
5

Tengo un código enhebrado donde cada hilo necesita escribir en el mismo archivo. Para evitar problemas de concurrencia, estoy usando un objeto de bloqueo.Acceso a recursos multiproceso: ¿dónde pongo mis bloqueos?

Mi pregunta es si estoy usando el bloqueo correctamente. Si configuro el bloqueo dentro de cada hilo, ¿ese bloqueo es global o solo específico para ese hilo específico?

Básicamente, debería crear un bloqueo primero y pasar su referencia a cada hilo, o ¿Está bien establecido que desde el interior de la rosca como lo hago aquí:

import time 
from threading import Thread, Lock 

def main(): 
    for i in range(20): 
     agent = Agent(i) 
     agent.start() 

class Agent(Thread): 
    def __init__(self, thread_num): 
     Thread.__init__(self) 
     self.thread_num = thread_num 

    def run(self): 
     while True: 
      print 'hello from thread %s' % self.thread_num 
      self.write_result() 

    def write_result(self): 
     lock = Lock() 
     lock.acquire() 
     try: 
      f = open('foo.txt', 'a') 
      f.write('hello from thread %s\n' % self.thread_num) 
      f.flush() 
      f.close() 
     finally: 
      lock.release() 

if __name__ == '__main__': 
    main() 

Respuesta

3

Crear la cerradura fuera del método.

class Agent(Thread): 
    mylock = Lock() 
    def write_result(self): 
     self.mylock.acquire() 
     try: 
      ... 
     finally: 
      self.mylock.release() 

o si está usando Python> = 2.5:

class Agent(Thread): 
    mylock = Lock() 
    def write_result(self): 
     with self.mylock: 
      ... 

usar que con Python 2.5 debe importar el comunicado del futuro:

from __future__ import with_statement 
+0

Sí, lo movió fuera del método, pero aún lo está creando en el propio subproceso. ¿No es eso un problema? –

+0

@cgoldberd: se está creando como un atributo * class *, eso significa que se creará una sola para todos los hilos. Ese es un mejor lugar para mantenerlo porque todo permanece en la clase de subprocesos. – nosklo

+0

gotcha. Me gusta ese enfoque –

1

la cerradura() devuelve el método un objeto de bloqueo para cada llamada. Entonces cada hilo (en realidad cada llamada a write_result) tendrá un objeto de bloqueo diferente. Y no habrá bloqueo.

1

El bloqueo que se utiliza debe ser común a todos los hilos, o al menos asegurarse de que dos bloqueos no puedan bloquear el mismo recurso al mismo tiempo.

0

Estoy bastante seguro de que el bloqueo debe ser el mismo objeto para cada hilo. Pruebe esto:

import time 
from threading import Thread, Lock 

def main(): 
    lock = Lock() 
    for i in range(20): 
     agent = Agent(i, lock) 
     agent.start() 

class Agent(Thread, Lock): 
    def __init__(self, thread_num, lock): 
     Thread.__init__(self) 
     self.thread_num = thread_num 
     self.lock = lock 

    def run(self): 
     while True: 
      print 'hello from thread %s' % self.thread_num 
      self.write_result() 

    def write_result(self): 
     self.lock.acquire() 
     try: 
      f = open('foo.txt', 'a') 
      f.write('hello from thread %s\n' % self.thread_num) 
      f.flush() 
      f.close() 
     finally: 
      lock.release() 

if __name__ == '__main__': 
    main() 
+0

-1: eso no funcionará. – nosklo

+0

nosklo. ¿Por qué no funciona? El bloqueo se crea primero y luego pasa a cada hilo. –

+0

Lo siento, me olvidé de pasar realmente el objeto de bloqueo al constructor del agente. Corregido –

1

La instancia de bloqueo debe asociarse con la instancia del archivo.

En otras palabras, debe crear tanto el bloqueo como el archivo al mismo tiempo y pasar ambos a cada subproceso.

6

Por su caso de uso de un enfoque podría ser escribir una subclase file que encierra:

class LockedWrite(file): 
    """ Wrapper class to a file object that locks writes """ 
    def __init__(self, *args, **kwds): 
     super(LockedWrite, self).__init__(*args, **kwds) 
     self._lock = Lock() 

    def write(self, *args, **kwds): 
     self._lock.acquire() 
     try: 
      super(LockedWrite, self).write(*args, **kwds) 
     finally: 
      self._lock.release() 

para utilizar en su código sólo reemplazar las funciones siguientes:

def main(): 
    f = LockedWrite('foo.txt', 'a') 

    for i in range(20): 
     agent = Agent(i, f) 
     agent.start() 

class Agent(Thread): 
    def __init__(self, thread_num, fileobj): 
     Thread.__init__(self) 
     self.thread_num = thread_num 
     self._file = fileobj  

# ... 

    def write_result(self): 
     self._file.write('hello from thread %s\n' % self.thread_num) 

Este enfoque pone el bloqueo de archivos en el archivo en sí mismo que parece más limpio IMHO

+0

Excepto la parte inútil "filelock = Lock()", me gusta más esta solución que la mía. Sería aún mejor si de alguna manera interceptara otros intentos de abrir foo.txt para escribir/anexar y regresar al objeto original LockedWrite. –

+0

@Joseph: eliminó la parte inútil. – nosklo

+0

Tienes mi voto. –

1

Puede simplificar las cosas un poco (a costa de un poco más de sobrecarga) mediante la designación de una sola amenaza d (probablemente creado exclusivamente para este fin) como el único hilo que escribe en el archivo, y haga que todos los otros hilos deleguen al escritor de archivos colocando la cadena que desean agregar al archivo en un objeto queue.Queue.

Las colas tienen todo el bloqueo incorporado, por lo que cualquier subproceso puede llamar de manera segura Queue.put() en cualquier momento. El escritor de archivos sería el único hilo que llama al Queue.get(), y es probable que pase la mayor parte del tiempo bloqueando esa llamada (con un tiempo de espera razonable para permitir que el hilo responda limpiamente a una solicitud de cierre). Todos los problemas de sincronización serán manejados por la cola, y se le ahorrará tener que preocuparse de si ha olvidado alguna adquisición/liberación de bloqueo en alguna parte ...:)

+0

Normalmente hago esto con Colas y un solo escritor también. Solo necesitaba una aclaración sobre los bloqueos. –

Cuestiones relacionadas