我最近问了一些关于 的问题TVar
,但我仍然对活锁感到担忧。
于是我想到了这个结构:
- 每个事务都有一个唯一的优先级(可能按创建顺序分配)。
- 事务尝试获取它们访问的数据的读/写锁。自然,同时读取是可以的,但是一个写入锁会排除所有其他锁(读取和写入)。
- 假设事务 A 比事务 B 具有更高的优先级。如果 A 持有锁,B 等待,但如果 B 持有锁并且 A 想要它,则 B 从锁中启动,A 获得它,然后事务 B 重新启动(如 with
TVar
)。然而,B 保持其当前的重试优先级。 - 当一个锁被释放并且有事务在等待时,它会转到最高优先级的事务,其余的继续等待。
我相信这个系统可以防止死锁,但也可以防止饥饿(不像TVar
)。我想知道是否有人实施了这样的系统,因为它似乎相当明显,我不想重新发明轮子。
当然,这种方法可以很容易地扩展为允许用户指定优先级。
Priority 可以是 pair (user_supplied_prio, auto_increment)
,具有user_supplied_prio
优先权,但同等优先级的任务按 FIFO 顺序解决。
评论/解决方案:
实际上,当我想到它时,我所描述的已经存在于 Haskell 中,只需使用一个IORef
包裹所有数据的方法,并且仅使用atomicModifyIORef
. 这atomicModifyIORef
将确保按顺序应用事务。然而,有人可能会认为这意味着数据结构是顺序的(即实际上仅限于一个线程),但由于惰性,它实际上是并行的。
为了解释这一点,考虑一个昂贵的函数f
。我们将把它应用到Data.Map
键为“foo”的数据上。
这替换(foo -> x)
为(foo -> future(f x))
. 该线程将继续解决(f x)
实际情况,但同时我们可以将 g 应用于“bar”。由于将g应用于“bar”不需要“foo”的结果,我们可以同时解决这个问题。
没有死锁,没有饥饿,最终所有事务都将被处理(大致按照它们被接收的顺序)。