Python shelve module question

后端 未结 3 2043
忘了有多久
忘了有多久 2020-12-17 01:48

Does the Python shelve module have any protection built in to make sure two processes aren\'t writing to a file at the same time?

相关标签:
3条回答
  • 2020-12-17 02:02

    As per the top answer, it's not safe to have multiple writers to the shelve. My approach to making shelves safer is to write a wrapper that takes care of opening and accessing shelve elements. The wrapper code looks something like this:

    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()
    
    0 讨论(0)
  • 2020-12-17 02:23

    The shelve module uses an underlying database package (such as dbm, gdbm or bsddb) .

    The restrictions pragraph says (my emphasis):

    The shelve module does not support concurrent read/write access to shelved objects. (Multiple simultaneous read accesses are safe.) When a program has a shelf open for writing, no other program should have it open for reading or writing. Unix file locking can be used to solve this, but this differs across Unix versions and requires knowledge about the database implementation used.

    Conclusion: it depends on OS and the underlying DB. To keep things portable, do not build on concurrency.

    0 讨论(0)
  • 2020-12-17 02:23

    I've implemented Ivo's approach as a context manager, for anyone interested:

    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
    
    0 讨论(0)
提交回复
热议问题