2010-05-14 33 views
1

我正在使用Dictionary作为后备存储的线程安全集合。锁定函数是否可用于实现线程安全枚举?

在C#中,你可以做到以下几点:

private IEnumerable<KeyValuePair<K, V>> Enumerate() { 
    if (_synchronize) { 
     lock (_locker) { 
      foreach (var entry in _dict) 
       yield return entry; 
     } 
    } else { 
     foreach (var entry in _dict) 
      yield return entry; 
    } 
    } 

我发现这样做在F#的唯一方法是使用监控,如:

let enumerate() = 
     if synchronize then 
      seq { 
       System.Threading.Monitor.Enter(locker) 
       try for entry in dict -> entry 
       finally System.Threading.Monitor.Exit(locker) 
      } 
     else seq { for entry in dict -> entry } 

可以这样用做锁定功能?或者,有没有更好的方法来做到这一点?我不认为返回迭代集合的副本将工作,因为我需要绝对同步。

+0

我同意@kvb,原来的代码/设计充其量是可疑的。 – Brian 2010-05-14 16:47:15

回答

4

我不认为你可以用锁定功能做同样的事情,因为你会试图从它内部屈服。话虽如此,这在任何一种语言中都看起来像是一种危险的方法,因为这意味着锁可以保持任意时间量(例如,如果一个线程调用Enumerate()但并未一直列举产生的IEnumerable<_>,那么锁将继续保持)。

它可能更有意义反转逻辑,提供沿着线的iter方法:

let iter f = 
    if synchronize then 
    lock locker (fun() -> Seq.iter f dict) 
    else 
    Seq.iter f dict 

这带来的迭代您的控制下背,确保序列被完全重复(假设f不会阻止,这在任何情况下似乎都是必要的假设),并且锁定在此后立即释放。

编辑

这里是代码的例子,可能永远持有锁。

let cached = enumerate() |> Seq.cache 
let firstFive = Seq.take 5 cached |> Seq.toList 

我们已经开始锁定,以便开始枚举前5项。然而,我们没有继续完成其余的顺序,所以锁定不会被释放(也许我们会根据用户的反馈或其他方式来列举其余的方式,在这种情况下锁定会最终被释放) 。

在大多数情况下,正确编写的代码将确保它处理原始枚举器,但是一般情况下无法保证。因此,你的序列表达式应该被设计为健壮的,只能被部分枚举。如果你打算要求你的调用者一次枚举所有的集合,那么迫使他们通过你的函数来应用到每个元素要比返回他们可以随意列举的序列要好。

+0

很棒的建议。谢谢。我很好奇一个线程如何不完全枚举集合可以保持锁定,除非你的意思是在for循环中调用Thread.Sleep(很长一段时间)。否则,它看起来会跳出循环,在枚举器上调用Dispose并释放锁。 – Daniel 2010-05-14 18:12:47

+0

@Daniel - 我已经添加了一个不释放锁的示例。如果调用代码始终使用for循环,则可能安全,但还有其他方法可以使用IEnumerables。 – kvb 2010-05-14 19:05:05

+0

感谢您的补充说明。 – Daniel 2010-05-14 19:13:16

4

我同意kvb的代码是可疑的,你可能不希望来保持锁定。但是,有一种方法可以使用use关键字以更舒适的方式编写锁定。值得一提的是,它可能在其他情况下有用。

您可以编写开始持有锁,并返回IDisposable功能,从而释放当它被设置在锁:

let makeLock locker = 
    System.Threading.Monitor.Enter(locker) 
    { new System.IDisposable with 
     member x.Dispose() = 
     System.Threading.Monitor.Exit(locker) } 

然后你就可以,例如写:

let enumerate() = seq { 
    if synchronize then  
    use l0 = makeLock locker 
    for entry in dict do 
     yield entry  
    else 
    for entry in dict do 
     yield entry } 

这是基本上使用use关键字来实现C#,如lock,它具有相似的属性(允许您在离开作用域时执行某些操作)。所以,这更接近原始的C#版本的代码。

相关问题