假设我有这个功能(假设我以线程安全的方式访问Cache):
object GetCachedValue(string id)
{
if (!Cache.ContainsKey(id))
{
//long running operation to fetch the value for id
object value = GetTheValueForId(id);
Cache.Add(id, value);
}
return Cache[id];
}
我想阻止两个线程同时运行“长时间运行的操作”获取相同的值。显然我可以将整个东西包装在一个lock()中,但是整个函数会阻塞而不管值是什么,我希望两个线程能够执行长时间运行操作,只要它们正在寻找不同的id。
是否存在基于值锁定的内置锁定机制,因此一个线程可以在另一个线程完成长时间运行操作时阻塞,因此我不需要执行两次(或者N次)?理想情况下,只要在一个线程中执行长时间运行操作,其他线程就不应该为相同的id值执行此操作。
我可以通过将id放在HashSet中然后在操作完成后删除它来自行滚动,但这看起来像是一个黑客。
答案 0 :(得分:7)
我会在这里使用Lazy<T>
。下面的代码将锁定缓存,将Lazy
放入缓存并立即返回。 长时间运行将以线程安全的方式执行一次。
new Thread(() => Console.WriteLine("1-" + GetCachedValue("1").Value)).Start();
new Thread(() => Console.WriteLine("2-" + GetCachedValue("1").Value)).Start();
Lazy<object> GetCachedValue(string id)
{
lock (Cache)
{
if (!Cache.ContainsKey(id))
{
Lazy<object> lazy = new Lazy<object>(() =>
{
Console.WriteLine("**Long Running Job**");
Thread.Sleep(3000);
return int.Parse(id);
},
true);
Cache.Add(id, lazy);
Console.WriteLine("added to cache");
}
return Cache[id];
}
}
答案 1 :(得分:1)
在这种情况下,我希望有这样的界面
using (SyncDispatcher.Enter(id))
{
//any code here...
}
所以我可以执行任何代码,如果id相同,它将是线程安全的。 如果我需要从Cache获取值,我可以直接进行,因为没有并发调用。
我对SyncDispatcher的实现是这样的:
public class SyncDispatcher : IDisposable
{
private static object _lock = new object();
private static Dictionary<object, SyncDispatcher> _container = new Dictionary<object, SyncDispatcher>();
private AutoResetEvent _syncEvent = new AutoResetEvent(true);
private SyncDispatcher() { }
private void Lock()
{
_syncEvent.WaitOne();
}
public void Dispose()
{
_syncEvent.Set();
}
public static SyncDispatcher Enter(object obj)
{
var objDispatcher = GetSyncDispatcher(obj);
objDispatcher.Lock();
return objDispatcher;
}
private static SyncDispatcher GetSyncDispatcher(object obj)
{
lock (_lock)
{
if (!_container.ContainsKey(obj))
{
_container.Add(obj, new SyncDispatcher());
}
return _container[obj];
}
}
}
简单测试:
static void Main(string[] args)
{
new Thread(() => Execute("1", 1000, "Resource 1")).Start();
new Thread(() => Execute("2", 200, "Resource 2")).Start();
new Thread(() => Execute("1", 0, "Resource 1 again")).Start();
}
static void Execute(object id, int timeout, string message)
{
using (SyncDispatcher.Enter(id))
{
Thread.Sleep(timeout);
Console.WriteLine(message);
}
}
答案 2 :(得分:0)
将锁定移至评论所在的位置。我认为您需要维护当前正在执行的长时间运行操作的列表,并锁定对该列表的访问,并且只有GetValueForId
如果您正在寻找id
因为不在那个清单中。我会尝试鞭打一些东西。
private List<string> m_runningCacheIds = new List<string>();
object GetCachedValue(string id)
{
if (!Cache.ContainsKey(id))
{
lock (m_runningCacheIds) {
if (m_runningCacheIds.Contains(id)) {
// Do something to wait until the other Get is done....
}
else {
m_runningCacheIds.Add(id);
}
}
//long running operation to fetch the value for id
object value = GetTheValueForId(id);
Cache.Add(id, value);
lock (m_runningCacheIds)
m_runningCacheIds.Remove(id);
}
return Cache[id];
}
当线程在另一个线程上等待获取值时,仍然存在线程将要执行的操作的问题。
答案 3 :(得分:0)
我在那种情况下使用Mutex作为:
object GetCachedValue(string Key)
{
// note here that I use the key as the name of the mutex
// also here you need to check that the key have no invalid charater
// to used as mutex name.
var mut = new Mutex(true, key);
try
{
// Wait until it is safe to enter.
mut.WaitOne();
// here you create your cache
if (!Cache.ContainsKey(Key))
{
//long running operation to fetch the value for id
object value = GetTheValueForId(Key);
Cache.Add(Key, value);
}
return Cache[Key];
}
finally
{
// Release the Mutex.
mut.ReleaseMutex();
}
}
注意:
答案 4 :(得分:-2)
这不是世界上最优雅的解决方案,但我通过双重检查和锁定来解决这个问题:
object GetCachedValue(string id)
{
if (!Cache.ContainsKey(id))
{
lock (_staticObj)
{
if (!Cache.ContainsKey(id))
{
//long running operation to fetch the value for id
object value = GetTheValueForId(id);
Cache.Add(id, value);
}
}
}
return Cache[id];
}