并发通用数据结构,没有死锁或资源匮乏

我最近问了一些有关TVar的问题,我仍然对livelock感到担忧。

所以我想到了这个结构:

  1. 每个事务都有一个唯一的优先级(可能是按创build顺序分配的)。
  2. 事务尝试获取对其访问的数据的读/写locking。 自然地,同时读取是可以的,但是一个写入locking排除所有其他(读取和写入)。
  3. 说事务A的优先级比事务B的优先级高。如果A持有锁,B等待,但是如果B持有锁并且A需要它,则B从锁中引导,A获得它,并且事务B重新启动(如同TVar ) 。 然而B保持当前优先重试。
  4. 当一个锁被释放并且等待事务时,它将进入最高优先级的事务,其余的继续等待。

我相信这个系统可以防止死锁,但也可以防止饥饿(与TVar不同)。 我想知道是否有人实施了这样一个系统,因为它似乎相当明显,我不想重新发明轮子。

当然,这样的方法很容易扩展到允许用户指定优先级。

优先级可以是pair (user_supplied_prio, auto_increment)user_supplied_prio优先,但优先级相同的任务按FIFO顺序parsing。

评论/解决scheme:

实际上,当我想到这一点时,Haskell中已经存在,只需要使用一个IORef包装所有的数据,并且只使用atomicModifyIORefatomicModifyIORef将确保交易按顺序应用。 但是,有人可能会认为这意味着数据结构是顺序的(即有效地限于一个线程),但实际上由于懒惰而是并行的。

为了解释这一点,考虑一个昂贵的函数f 。 我们打算把这个应用到一个Data.Map的关键字“foo”的数据。

(foo -> future(fx))replace(foo -> x) (foo -> future(fx)) 。 这个线程将继续确定(fx)实际是什么,但同时我们可以将g应用到“bar”中。 由于将g应用于“bar”并不需要“foo”的结果,因此我们可以同时处理这个问题。

没有死锁,没有饥饿,最终所有交易将被处理(大致按照他们收到的顺序)。

您可以设置一个工作线程以确定的方式处理所有的请求,所以没有人会饿死。 这个策略将是合理的效率和免疫活锁。

 -- yes, this is a horrible name createManagerFactory :: a -> IO ((IO a), IO (((a -> a) -> IO a))) 

IO a是一个安全快速的查询操作,只读STM操作。 (a – > a)是修改值的纯函数,所以((a – > a) – > IO a)是一个采取修饰符函数,安全地修改该值并返回新值的操作。

运行一次初始化工厂。

 (query, modifierFactory) <- createManagerVactory initValue 

然后为每个线程运行这个来产生一个新的修饰符。

 myModify <- modifierFactory 

createManagerFactory将执行以下操作:

  • 创build一个包含initValue的TVar(称之为valueTVar)。
  • 创build一个包含一个空TVAR集合的TVar(a(a – > a))(称之为modifyTVarCollection)
  • 返回(primefaces$ readTVar valueTVar)作为“查询”的结果
  • 返回一个了解modifyTVarCollection的modifierFactory

modifierFactory会做到这一点:

  • 创build一个新的TVar(a(a – > a))(称为modifyTVar),用valueTVar的当前值初始化为(Left a),并将其添加到modifyTVarCollection
  • 返回一个在一个STM操作中加载(Right(a – > a))到modifyTVar的修饰符操作,然后在另一个STM操作中重试,直到modifyTVar包含(Left a)结果值,然后返回该值。

工作线程会运行这个循环:

  • 在一个动作中,从modifyTVarCollection获取所有查询的TVar,并检查它们的值。 如果它们都包含(Left a)值,则重试(直到其他线程使用修饰符函数加载其modifytwar,或者modifierFactory创build新的modifierTVar并将其添加到集合中)。 返回所有包含Right的modifyTVars的列表(a – > a)
  • 迭代所有返回的modifyTVars。 对于每个TVar,执行读取修饰符函数的操作,安全地执行修改,并将结果放回到modifyTVar中作为(Left a)