shelve在只读多进程中是否不安全?

7 投票
1 回答
2192 浏览
提问于 2025-04-18 04:56

shelve模块的文档在限制部分提到:

shelve模块不支持对存储对象的同时读写访问。(多个同时的读取是安全的。)

根据我的理解,这意味着只要我不试图让多个进程同时写入同一个存储,就没问题。多个进程只用这个存储来读取数据应该是安全的,对吧?

显然不是。经过一番挣扎,我最终得到了一个测试案例,似乎显示出在异步读取存储时会出现一些非常糟糕的情况。以下是这个脚本:

  1. 创建一个Shelf,并用"i" : 2*i填充它,i从1到10。
  2. 读取所有这些值,以确保它们被正确存储。
  3. 启动进程从存储文件中检索每个键的值,并报告是否成功获取到值。

    import multiprocessing
    import shelve
    
    SHELF_FILE = 'test.shlf'
    
    def store(key, obj):
        db = shelve.open(SHELF_FILE, 'w')
        db[key] = obj
        db.close()
    
    def load(key):
        try:
            db = shelve.open(SHELF_FILE, 'r')
            n = db.get(key)
            if n is not None:
                print('Got result {} for key {}'.format(n, key))
            else:
                print('NO RESULT for key {}'.format(key))
        except Exception as e:
            print('ERROR on key {}: {}'.format(key, e))
        finally:
            db.close()
    
    if __name__ == '__main__':
        db = shelve.open(SHELF_FILE, 'n') # Create brand-new shelf
        db.close()
    
        for i in range(1, 11): # populate the new shelf with keys from 1 to 10
            store(str(i), i*2)
    
        db = shelve.open(SHELF_FILE, 'r') # Make sure everything got in there.
        print(', '.join(key for key in db)) # Should print 1-10 in some order
        db.close()
    
        # read each key's value from the shelf, asynchronously
        pool = multiprocessing.Pool()
        for i in range(1, 11):
            pool.apply_async(load, [str(i)])
        pool.close()
        pool.join()
    

这里预期的输出自然是2, 4, 6, 8,一直到20(顺序可以不一样)。然而,随机的值无法从存储中获取,有时请求甚至导致shelve完全崩溃。实际输出看起来像这样:("NO RESULT"行表示返回None的键):

6, 7, 4, 5, 2, 3, 1, 10, 8, 9
ERROR on key 3: need 'c' or 'n' flag to open new db
ERROR on key 6: need 'c' or 'n' flag to open new db
Got result 14 for key 7
NO RESULT for key 10
Got result 2 for key 1
Got result 4 for key 2
NO RESULT for key 8
NO RESULT for key 4
NO RESULT for key 5
NO RESULT for key 9

根据错误信息,我的直觉是,也许外部资源(可能是.dir文件?)没有被正确写入磁盘(或者可能被其他进程删除了?)。即便如此,我也期待在进程等待磁盘资源时会出现延迟,而不是这些“哦,我想它不在那儿”或“你在说什么,这根本不是一个存储文件”的结果。坦白说,我也不指望这些文件会有任何写入,因为工作进程只使用只读连接……

我是不是漏掉了什么,还是说shelve在多进程环境中根本就无法使用?

这是在Windows 7上运行的Python 3.3 x64,如果这有关系的话。

1 个回答

2

shelve.open()的说明中,有一个提醒:

这个函数可以打开一个持久化的字典。你指定的文件名是底层数据库的基础文件名。作为一个附带效果,文件名可能会增加一个扩展名,并且可能会创建多个文件。

试着把一个已经打开的shelve(而不是文件名)传给线程池,看看行为是否会有所不同。不过,我在2.7版本和Win7-64上没有复现这个问题(输出当然是乱七八糟的)。

撰写回答