Pregunta del módulo de estantería de Python
Pregunta
¿El módulo de estantería Python tiene alguna protección integrada para asegurarse de que dos procesos no estén escribiendo en un archivo al mismo tiempo?
Solución
El módulo de estantería utiliza un paquete de base de datos subyacente (como dbm, gdbm o bsddb).
El restricciones pragraph dice (mi énfasis):
El módulo de almacenamiento no admite acceso de lectura / escritura concurrente a objetos archivados . (Múltiples accesos de lectura simultáneos son seguros). Cuando un programa tiene un estante abierto para escribir, ningún otro programa debe tenerlo abierto para leer o escribir. El bloqueo de archivos Unix se puede utilizar para resolver esto, pero esto difiere entre las versiones de Unix y requiere conocimiento sobre la implementación de la base de datos utilizada.
Conclusión: depende del sistema operativo y la base de datos subyacente. Para mantener las cosas portátiles, no construyas sobre la concurrencia.
Otros consejos
Según la respuesta principal, no es seguro tener múltiples escritores en la estantería. Mi enfoque para hacer que los estantes sean más seguros es escribir un contenedor que se encargue de abrir y acceder a los elementos de los estantes. El código contenedor se parece a esto:
def open(self, mode=READONLY):
if mode is READWRITE:
lockfilemode = "a"
lockmode = LOCK_EX
shelve_mode = 'c'
else:
lockfilemode = "r"
lockmode = LOCK_SH
shelve_mode = 'r'
self.lockfd = open(shelvefile+".lck", lockfilemode)
fcntl.flock(self.lockfd.fileno(), lockmode | LOCK_NB)
self.shelve = shelve.open(shelvefile, flag=shelve_mode, protocol=pickle.HIGHEST_PROTOCOL))
def close(self):
self.shelve.close()
fcntl.flock(self.lockfd.fileno(), LOCK_UN)
lockfd.close()
He implementado el enfoque de Ivo como administrador de contexto, para cualquier persona interesado:
from contextlib import contextmanager, closing
from fcntl import flock, LOCK_SH, LOCK_EX, LOCK_UN
import shelve
@contextmanager
def locking(lock_path, lock_mode):
with open(lock_path, 'w') as lock:
flock(lock.fileno(), lock_mode) # block until lock is acquired
try:
yield
finally:
flock(lock.fileno(), LOCK_UN) # release
class DBManager(object):
def __init__(self, db_path):
self.db_path = db_path
def read(self):
with locking("%s.lock" % self.db_path, LOCK_SH):
with closing(shelve.open(self.db_path, "c", 2)) as db:
return dict(db)
def cas(self, old_db, new_db):
with locking("%s.lock" % self.db_path, LOCK_EX):
with closing(shelve.open(self.db_path, "c", 2)) as db:
if old_db != dict(db):
return False
db.clear()
db.update(new_db)
return True