如何在 C# 中包含对象列表的字典上使用锁?
我有以下课程:
public static class HotspotsCache
{
private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<int, List<HotSpot>>();
private static object Lock = new object();
public static List<HotSpot> GetCompanyHotspots(short companyId)
{
lock (Lock)
{
if (!_companyHotspots.ContainsKey(companyId))
{
RefreshCompanyHotspotCache(companyId);
}
return _companyHotspots[companyId];
}
}
private static void RefreshCompanyHotspotCache(short companyId)
{
....
hotspots = ServiceProvider.Instance.GetService<HotspotsService>().GetHotSpots(..);
_companyHotspots.Add(companyId, hotspots);
....
}
我遇到的问题是在 RefreshCompanyHotspotCache 方法中获取热点的操作需要花费大量时间。因此,当一个线程正在对某个 CompanyId 执行缓存刷新时,所有其他线程都会等待该操作完成,尽管可能有线程正在请求另一个 companyId 的热点列表,而该列表已加载到该 CompanyId 中。字典。我希望最后这些线程不被锁定。我还希望请求尚未加载到缓存中的公司的热点列表的所有线程都等待,直到列表完全检索并加载到字典中。
有没有办法只锁定正在读取/写入特定 companyId(正在刷新)的缓存的线程,并让正在请求另一家公司数据的其他线程完成其工作?
我的想法是使用锁数组
lock (companyLocks[companyId])
{
...
}
但这并没有解决任何问题。与一家公司打交道的线程仍在等待为其他公司刷新缓存的线程。
I have the following class:
public static class HotspotsCache
{
private static Dictionary<short, List<HotSpot>> _companyHotspots = new Dictionary<int, List<HotSpot>>();
private static object Lock = new object();
public static List<HotSpot> GetCompanyHotspots(short companyId)
{
lock (Lock)
{
if (!_companyHotspots.ContainsKey(companyId))
{
RefreshCompanyHotspotCache(companyId);
}
return _companyHotspots[companyId];
}
}
private static void RefreshCompanyHotspotCache(short companyId)
{
....
hotspots = ServiceProvider.Instance.GetService<HotspotsService>().GetHotSpots(..);
_companyHotspots.Add(companyId, hotspots);
....
}
The issue that I'm having is that the operation of getting the hotspots, in RefreshCompanyHotspotCache method, takes a lot of time . So while one thread is performing the cache refresh for a certain CompanyId, all the other threads are waiting until this operation is finished, although there could be threads that are requesting the list of hotspots for another companyId for which the list is already loaded in the dictionary. I would like these last threads not be locked. I also want that all threads that are requesting the list of hotspots for a company that is not yet loaded in the cache to wait until the list is fully retrieved and loaded in the dictionary.
Is there a way to lock only the threads that are reading/writing the cache for certain companyId (for which the refresh is taking place) and let the other threads that are requesting data for another company to do their job?
My thought was to use and array of locks
lock (companyLocks[companyId])
{
...
}
But that didn't solve anything. The threads dealing with one company are still waiting for threads that are refreshing the cache for other companies.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
使用 Snowbear 也提到的双重检查锁定机制 - 这将防止您的代码在实际上不需要时锁定。
根据您为每个客户端分配一个锁的想法,我过去使用过这种机制,尽管我使用了锁字典。我制作了一个实用程序类,用于从钥匙获取锁定对象:
所以只需按以下方式使用它......
Use the Double-checked lock mechanism also mentioned by Snowbear - this will prevent your code locking when it doesn't actually need to.
With your idea of an individual lock per client, I've used this mechanism in the past, though I used a dictionary of locks. I made a utility class for getting a lock object from a key:
So simply use this in the following manner...
您只锁定 1 个线程并让其更新,而其他人都使用旧列表怎么样?
How about you only lock 1 thread, and let that update, while everyone else uses the old list?
您研究过 ReaderWriterLockSlim 吗?这应该能够让您获得更细粒度的锁定,只在需要时才采用写锁。
您可能需要注意的另一件事是虚假共享。我不知道锁是如何准确实现的,但是如果您锁定数组中的对象,它们在内存中必然彼此靠近,可能会将它们放在同一个缓存行上,因此锁可能不会按照您的预期运行。
另一个想法是,如果您将最后一个代码片段更改为
锁语句,那么会发生什么,这可能比预期的更多。
国杰
Have you looked into ReaderWriterLockSlim? That should be able to let get finer grained locking where you only take a writelock when needed.
Another thing you may need to look out for is false sharing. I don't know how a lock is implemented exactly but if you lock on objects in an array they're bound to be close to each other in memory, possibly putting them on the same cacheline, so the lock may not behave as you expect.
Another idea, what happens if you change the last code snippet to
could be the lock statement wraps more here than intended.
GJ
新想法,仅在创建列表时锁定列表。
如果你能保证每个公司至少有一个热点,那么可以这样做:
由于字典在初始创建后正在修改,因此不需要对其进行任何锁定。我们只需要在填充各个列表时锁定它们,读取操作不需要锁定(包括初始 Count == 0)。
New idea, with locking just the lists as they are created.
If you can guarantee that each company will have at least one hotspot, do this:
Since the dictionary is being modified after its initial creation, no need to do any locking on it. We only need to lock the individual lists as we populate them, the read operation needs no locking (including the initial Count == 0).
如果您能够使用 .NET 4,那么答案很简单 - 使用
ConcurrentDictionary
相反,让它为您处理并发详细信息:如果您无法使用 .NET 4,那么您使用多个更细粒度的锁的想法是一个好的:
If you're able to use .NET 4 then the answer is straightforward -- use a
ConcurrentDictionary<K,V>
instead and let that look after the concurrency details for you:If you're not able to use .NET 4 then your idea of using several more granular locks is a good one: