代码之家  ›  专栏  ›  技术社区  ›  Daniel

lock函数能否用于实现线程安全枚举?

  •  1
  • Daniel  · 技术社区  · 16 年前

    我正在开发一个线程安全集合,它使用字典作为后备存储。

    在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 }
    

    是否可以使用锁定功能完成此操作?或者,总的来说,有没有更好的方法来做到这一点?我不认为返回集合的副本进行迭代会有效,因为我需要绝对同步。

    2 回复  |  直到 16 年前
        1
  •  4
  •   kvb    16 年前

    我不认为您能够对锁函数做同样的事情,因为您将尝试从它内部屈服。尽管如此,在这两种语言中,这看起来都是一种危险的方法,因为这意味着锁可以被持有任意时间(例如,如果一个线程调用 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项,我们已经获取了锁。但是,我们没有继续执行序列的其余部分,因此锁不会被释放(可能我们稍后会根据用户反馈或其他内容枚举其余的方法,在这种情况下,锁最终会被释放)。

    在大多数情况下,正确编写的代码将确保它处理原始枚举器,但一般来说,无法保证这一点。因此,序列表达式的设计应该是健壮的,只允许部分枚举。如果您打算要求调用方同时枚举集合,那么强制它们将函数传递给您以应用于每个元素,这比返回一个序列要好,因为它们可以按照自己的意愿枚举该序列。

        2
  •  4
  •   Tomas Petricek    16 年前

    我同意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 }
    

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

    推荐文章