将 append only dict 写入磁盘避免线程锁定
Writing an append only dict to disk avoiding thread locking
我有一个字典,它有 20,000 个键,字典的总大小是 150MB。我每小时通过 pickle 将 dict 转储到磁盘,然后在程序启动时加载 pickle 文件。
下面是写代码的要点
cache_copy = copy.deepcopy(self.cache)
#self.cache is the dict
pickle.dump(cache_copy, cache_file, pickle.HIGHEST_PROTOCOL)
有时会出现以下错误
cache_copy = copy.deepcopy(self.cache)
File "/usr/lib/python2.7/copy.py", line 163, in deepcopy
y = copier(x, memo)
File "/usr/lib/python2.7/copy.py", line 256, in _deepcopy_dict
for key, value in x.iteritems():
RuntimeError: dictionary changed size during iteration
最好的方法是什么?我想真正避免线程锁定,因为它会使代码变得复杂。如果确实有必要,锁定应该尽可能 minimal/simple 并允许一些并发。我的代码中有几个约束可以在这个方向上有所帮助:
- 多个线程读写dict()。但是,在所有写入中,只添加 (key,value) 对。 (key, value) 对永远不会被删除或修改
- 我愿意将数据结构从 dict() 更改为其他内容。它应该具有快速内存查找和写入的功能
- 我不介意陈旧的写作。因此,如果 dict() 有一些追加,并且我们写入几秒前的 dict 快照就可以了。
TL;DR 编辑
为了避免在酸洗过程中锁定字典,创建一个复制字典的列表:
# When you do this:
cache["new_key"] = "new_value"
# Also do this:
cache_list.append(("new_key", "new_value"))
然后 pickle 列表。
最好附加到 cache_file
而不是覆盖它。这样,您可以在每次写入文件后 cache_list.clear()
,避免内存和磁盘写入的浪费。但是当某些线程在酸洗后立即写入列表时可能会出现错误,然后该值被 clear
ed。如果这只是一个缓存,也许您可以接受丢失一些值。如果不是,请使用一些锁或干脆不 clear
列表。我关于双倍内存使用的错误是因为列表没有 deepcopy
数据,它只存储 20000 个元组,每个元组有 2 个引用。
原回答
如果你想迭代、复制或 pickle 你的字典,你需要锁定所有的写入。但是,如果您真的不想锁定使用字典的线程并且您不介意将内存使用量加倍,我建议保留一个键值对列表(它复制了你的字典),该列表的锁和写入该列表的队列:
# When you do this:
cache["new_key"] = "new_value"
# Also do this:
if not list_is_locked:
cache_list.append(("new_key", "new_value"))
else:
# Write here immediately instead of waiting for list to unlock
queue.append(("new_key", "new_value"))
当腌制时间到来时,您锁定并腌制该列表而不是字典:
list_is_locked = True
# pickle cache_list here
list_is_locked = False
cache_list.extend(queue)
queue.clear()
我不太熟悉 pickle
,但如果可以附加到 cache_file
而不是覆盖它,您也可以在 pickle 后清除列表。这将有助于避免巨大的内存负担。
我有一个字典,它有 20,000 个键,字典的总大小是 150MB。我每小时通过 pickle 将 dict 转储到磁盘,然后在程序启动时加载 pickle 文件。 下面是写代码的要点
cache_copy = copy.deepcopy(self.cache)
#self.cache is the dict
pickle.dump(cache_copy, cache_file, pickle.HIGHEST_PROTOCOL)
有时会出现以下错误
cache_copy = copy.deepcopy(self.cache)
File "/usr/lib/python2.7/copy.py", line 163, in deepcopy
y = copier(x, memo)
File "/usr/lib/python2.7/copy.py", line 256, in _deepcopy_dict
for key, value in x.iteritems():
RuntimeError: dictionary changed size during iteration
最好的方法是什么?我想真正避免线程锁定,因为它会使代码变得复杂。如果确实有必要,锁定应该尽可能 minimal/simple 并允许一些并发。我的代码中有几个约束可以在这个方向上有所帮助:
- 多个线程读写dict()。但是,在所有写入中,只添加 (key,value) 对。 (key, value) 对永远不会被删除或修改
- 我愿意将数据结构从 dict() 更改为其他内容。它应该具有快速内存查找和写入的功能
- 我不介意陈旧的写作。因此,如果 dict() 有一些追加,并且我们写入几秒前的 dict 快照就可以了。
TL;DR 编辑
为了避免在酸洗过程中锁定字典,创建一个复制字典的列表:
# When you do this:
cache["new_key"] = "new_value"
# Also do this:
cache_list.append(("new_key", "new_value"))
然后 pickle 列表。
最好附加到 cache_file
而不是覆盖它。这样,您可以在每次写入文件后 cache_list.clear()
,避免内存和磁盘写入的浪费。但是当某些线程在酸洗后立即写入列表时可能会出现错误,然后该值被 clear
ed。如果这只是一个缓存,也许您可以接受丢失一些值。如果不是,请使用一些锁或干脆不 clear
列表。我关于双倍内存使用的错误是因为列表没有 deepcopy
数据,它只存储 20000 个元组,每个元组有 2 个引用。
原回答
如果你想迭代、复制或 pickle 你的字典,你需要锁定所有的写入。但是,如果您真的不想锁定使用字典的线程并且您不介意将内存使用量加倍,我建议保留一个键值对列表(它复制了你的字典),该列表的锁和写入该列表的队列:
# When you do this:
cache["new_key"] = "new_value"
# Also do this:
if not list_is_locked:
cache_list.append(("new_key", "new_value"))
else:
# Write here immediately instead of waiting for list to unlock
queue.append(("new_key", "new_value"))
当腌制时间到来时,您锁定并腌制该列表而不是字典:
list_is_locked = True
# pickle cache_list here
list_is_locked = False
cache_list.extend(queue)
queue.clear()
我不太熟悉 pickle
,但如果可以附加到 cache_file
而不是覆盖它,您也可以在 pickle 后清除列表。这将有助于避免巨大的内存负担。