使用任何一种基于树的持久性/不变性(即功能性)数据结构。正如@Raphael在评论中指出的那样,关键是要正确锁定。
基于功能/持久树的数据结构的好处是,您可以免费获得“快照”。假设您对数据结构使用了treap(随机二进制搜索树)。这是用Go语言编写的示例:https : //github.com/steveyen/gtreap。作者对此进行了描述:
不可变地,对陷阱的任何更新/删除都将返回一个新的陷阱,该新陷阱可以与先前的陷阱共享内部节点。创建后,此实现中的所有节点均为只读。这允许并发读取器与并发写入器安全地操作,因为修改只会创建新的数据结构,而不会修改现有的数据结构。这是实现MVCC或多版本并发控制的简单方法。
O(logn)
您使用锁来保护指向根的指针。由于数据结构是不可变的,因此可以同时进行读取,并且可以保存指向旧快照的指针。读为:
lock
tmp = ptr_to_root
unlock
value = search(tmp, <value to search for>)
return value
即使搜索可能需要一段时间,您在复制指针时只握住锁,因此搜索可以同时发生。
写为:
lock
old_ptr_to_root = ptr_to_root
ptr_to_root = insert(old_ptr_to_root, <new key/value pair>)
unlock
在此版本中,写入程序确实需要在创建树的新版本的整个过程中保持锁定。您可以通过将写入更改为以下内容来提高读取性能(有时会导致写入事务失败):
top:
lock
old_ptr_to_root = ptr_to_root
unlock
new_ptr_to_root = insert(old_ptr_to_root, <new key/value pair>)
lock
if (ptr_to_root == old_ptr_to_root) # make sure no other write happened in the interim
ptr_to_root = new_ptr_to_root
unlock
else # transaction fails, try again
unlock
goto top
如果您的编程语言具有原子比较和交换操作的原子变量,则您甚至可以做得更好(使其“无锁”)。(例如,使用C ++ 11。atomic<T*>
)