昨天我发现我们使用的简单缓存对象存在多线程问题:
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
所以我有两个问题:
答案 0 :(得分:1)
1等待处理阻止,直到发出信号。你需要在某个地方发出信号,告诉它等待句柄不阻止第一个线程获取访问权限。我相信如果你在创建waithandle的构造函数中发出了句柄信号,它会起作用。考虑它就像在waithandle中有一个信号槽一样,任何调用wait的线程都会等到它在离开等待调用之前消耗一个信号。
2在这种情况下,它可能不是最好的锁使用,如果两个线程试图读取已经在缓存中的值,那么一个将被阻塞,直到另一个完成。我会用一个读写器锁。这样多个线程可以同时读取缓存,并且可以在必要时升级到写入。
如果您不介意缓存中具有相同值的多个加载,则可以使用concurrentdictionary。任何需要尚未加载的值的线程都会加载它然后调用tryadd。在多个线程同时尝试访问相同的卸载值的情况下,所有这些线程都将执行调用GetRateFromDB的工作。
答案 1 :(得分:1)
Windows条款:
所以使用上面的AutoResetEvent
绝对不是你想要的。当一个线程调用其中一个Wait
方法时,它将阻塞,直到它收到来自另一个线程的信号。没有人发信号给你,所以你要永远等待。
SyncLock
语句使用了封面下的关键部分,并防止任何线程同时输入相同的代码块。这将为您提供所需的保护。但是因为你需要保护对Dictionary对象的所有访问以避免损坏,所以你需要在使用Dictionary对象的任何地方使用锁。
如前所述,ConcurrentDictionary
在这种情况下是一件好事,因为它本身内置了一个高度优化的Reader Writer锁。因此,您不必在整个代码库中添加一堆锁。但正如我的评论中所述,使用ConcurrentDictionary时仍然可能存在竞争条件。