更新:如果这个方法不是线程安全的,这是可以接受的,但我有兴趣学习如何使它保持线程安全.另外,key
如果我可以避免它,我不想锁定所有值的单个对象.
原始问题:假设我想编写一个带有键和函数的高阶函数,并检查对象是否已使用给定的密钥进行高速缓存.如果是,则返回缓存的值.否则,运行给定的函数并缓存并返回结果.
这是我的代码的简化版本:
public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
object cache = HttpContext.Current.Cache.Get(key);
//clearly not thread safe, two threads could both evaluate the below condition as true
//what can I lock on since the value of "key" may not be known at compile time?
if (cache == null)
{
T result = fn();
HttpContext.Current.Cache.Insert(key, result, null, expires, Cache.NoSlidingExpiration);
return result;
}
else
return (T)cache;
}
Run Code Online (Sandbox Code Playgroud)
另外,假设我key
在编译时不知道所有可能的值.
如何使这个线程安全?我知道我需要在这里引入锁定,以防止1+线程将我的条件评估为真,但我不知道要锁定什么.我读过的许多关于锁定的例子(例如Jon Skeet的文章)建议使用仅用于锁定的"虚拟"私有变量.在这种情况下,这是不可能的,因为密钥在编译时是未知的.我知道我可以通过为每个人使用相同的锁来轻松地使这个线程安全 …
我有一个场景,我必须保持引用计数对象的给定键ConcurrentDictionary
,如果引用计数达到0
,我想删除键.这必须是线程安全的,因此我打算使用ConcurrentDictionary
.
示例程序如下.在并发字典中,我有键和值,值是KeyValuePair,它保存我的自定义对象和引用计数.
ConcurrentDictionary<string, KeyValuePair<object, int>> ccd =
new ConcurrentDictionary<string, KeyValuePair<object, int>>();
// following code adds the key, if not exists with reference
// count for my custom object to 1
// if the key already exists it increments the reference count
var addOrUpdateValue = ccd.AddOrUpdate("mykey",
new KeyValuePair<object, int>(new object(), 1),
(k, pair) => new KeyValuePair<object, int>(pair.Key, pair.Value + 1));
Run Code Online (Sandbox Code Playgroud)
现在我想要一种方法来在引用计数达到0时删除密钥.我在想,删除带有ConcurrentDictionary
键和谓词的方法,如果谓词返回'true'则删除密钥.例.
ConcurrentDictionary.remove(TKey, Predicate<TValue> ).
Run Code Online (Sandbox Code Playgroud)
没有这样的方法ConcurrentDictionary
,问题是如何以线程安全的方式做同样的事情?
.net c# multithreading task-parallel-library concurrentdictionary
请考虑以下代码:
Dictionary<string, string> list = new Dictionary<string, string>();
object lockObj = new object();
public void MyMethod(string a) {
if (list.Contains(a))
return;
lock (lockObj) {
list.Add(a,"someothervalue");
}
}
Run Code Online (Sandbox Code Playgroud)
假设我MyMethod("mystring")
同时从不同的线程调用.
是否可能有多个线程(我们只需将其作为两个)同时输入if (!list.Contains(a))
语句(具有几个CPU周期差异),两个线程都被评估为false
,一个线程进入关键区域而另一个线程进入关键区域被锁定在外面,所以第二个线程进入并"mystring"
在第一个线程退出后再次添加到列表中,导致字典尝试添加重复键?
更新:正如Brian指出的那样,我最初的想法确实存在并发问题.这个ConcurrentDictionary<TKey, TValue>.AddOrUpdate
方法的签名有点模糊,可以让一个懒惰的思想家(比如我自己)相信所有东西 - 集合添加以及队列推送 - 会以某种方式同时发生,原子地(即,神奇地) ).
回想起来,有这种期望对我来说是愚蠢的.实际上,无论执行情况如何AddOrUpdate
,都应该很清楚,我的原始想法中仍会存在竞争条件,正如Brian所指出的那样:在添加到集合之前推送到队列,因此可能发生以下事件序列:
上述序列将导致集合中的项目不在队列中,从而有效地将该项目列入数据结构的黑名单.
现在,我想了一会儿,我开始认为以下方法可以解决这些问题:
public bool Enqueue(T item)
{
// This should:
// 1. return true only when the item is first added to the set
// 2. subsequently return false as long as the item is in the set;
// and it will not be removed until after it's popped
if (_set.TryAdd(item, true))
{
_queue.Enqueue(item);
return true;
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
以这种方式构造它, …
ConcurrentDictionary
我正在循环中填充 a Parallel.ForEach
:
var result = new ConcurrentDictionary<int, ItemCollection>();
Parallel.ForEach(allRoutes, route =>
{
// Some heavy operations
lock(result)
{
if (!result.ContainsKey(someKey))
{
result[someKey] = new ItemCollection();
}
result[someKey].Add(newItem);
}
}
Run Code Online (Sandbox Code Playgroud)
如何在不使用 lock 语句的情况下以线程安全的方式执行最后的步骤?
编辑: 假设这ItemCollection
是线程安全的。
c# parallel-processing task-parallel-library concurrentdictionary parallel.foreach