使用哪种锁定原语?

时间:2013-08-21 14:32:43

标签: .net multithreading waithandle

昨天我发现我们使用的简单缓存对象存在多线程问题:

 If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
      lvResult = Dictionary.Item(lsKey.ToLower)

  Else 'else retrieve from database, store, and return value
      lvResult = GetRateFromDB(voADO,
                               veRateType,
                               vdEffDate)
      Dictionary.Add(lsKey.ToLower, lvResult)

  End If

我们在asp.net网站上发现了这个问题。错误消息读取类似“你试图将值添加到已经存在的散列表。正如你从上面的代码中可以看出的那样,潜在的确定会出现这种情况。我对waithandles有些熟悉,并认为他们会解决问题所以我在班级宣布了我的等待句柄:

private Shared _waitHandle as new AutoResetEvent(True)

然后在具有问题的代码的特定部分中:

_waitHandle.Wait()
If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
    lvResult = Dictionary.Item(lsKey.ToLower)

Else 'else retrieve from database, store, and return value
    lvResult = GetRateFromDB(voADO,
                             veRateType,
                             vdEffDate)
     Dictionary.Add(lsKey.ToLower, lvResult)
End If
_waitHandle.Set()

由于某种原因,以上代码始终被阻止。甚至是第一个访问代码的线程。我玩了一段时间的东西,甚至尝试将waithandle设置为在构造函数中发出信号,但我永远无法让它工作。

我最终使用以下内容而不是正常工作:

SyncLock loLock
    If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value
        lvResult = Dictionary.Item(lsKey.ToLower)

    Else 'else retrieve from database, store, and return value
        lvResult = GetRateFromDB(voADO,
                                 veRateType,
                                 vdEffDate)
        Dictionary.Add(lsKey.ToLower, lvResult)

    End If
End SyncLock

所以我有两个问题:

  1. 为什么等待处理解决方案不起作用?
  2. 在这种情况下,SynLock是正确/优化的锁类型吗?

2 个答案:

答案 0 :(得分:1)

1等待处理阻止,直到发出信号。你需要在某个地方发出信号,告诉它等待句柄不阻止第一个线程获取访问权限。我相信如果你在创建waithandle的构造函数中发出了句柄信号,它会起作用。考虑它就像在waithandle中有一个信号槽一样,任何调用wait的线程都会等到它在离开等待调用之前消耗一个信号。

2在这种情况下,它可能不是最好的锁使用,如果两个线程试图读取已经在缓存中的值,那么一个将被阻塞,直到另一个完成。我会用一个读写器锁。这样多个线程可以同时读取缓存,并且可以在必要时升级到写入。

如果您不介意缓存中具有相同值的多个加载,则可以使用concurrentdictionary。任何需要尚未加载的值的线程都会加载它然后调用tryadd。在多个线程同时尝试访问相同的卸载值的情况下,所有这些线程都将执行调用GetRateFromDB的工作。

答案 1 :(得分:1)

Windows条款:

  • 事件”允许一个线程(或进程)发出另一个线程(或进程)信号。
  • 信号量”类似于事件,但它可用于发出(唤醒)特定数量的线程。
  • 关键部分”用于防止同时访问代码块。
  • Slim Lock ”类似于关键部分,但通常不允许拥有锁的线程多次输入(允许使用临界区)。
  • Reader Writer ”锁可让您灵活地以独占模式(类似于关键部分)或共享模式锁定对象,从而允许多个线程执行同一个块。
  • 为了完整起见,还有一个“ Mutex ”,它与关键部分非常相似,但可以跨进程共享。

所以使用上面的AutoResetEvent绝对不是你想要的。当一个线程调用其中一个Wait方法时,它将阻塞,直到它收到来自另一个线程的信号。没有人发信号给你,所以你要永远等待。

SyncLock语句使用了封面下的关键部分,并防止任何线程同时输入相同的代码块。这将为您提供所需的保护。但是因为你需要保护对Dictionary对象的所有访问以避免损坏,所以你需要在使用Dictionary对象的任何地方使用锁。

如前所述,ConcurrentDictionary在这种情况下是一件好事,因为它本身内置了一个高度优化的Reader Writer锁。因此,您不必在整个代码库中添加一堆锁。但正如我的评论中所述,使用ConcurrentDictionary时仍然可能存在竞争条件。