【发布时间】:2014-03-02 12:42:31
【问题描述】:
将我的代码移动到可移植框架中,我发现可移植类库(PCL)中根本没有可用的并发集合,这个discussion很好地解释了它。
但我仍然需要 并发字典 模拟在那里运行。 而且我对读取/包含性能比对修改性能更感兴趣。
【问题讨论】:
标签: .net silverlight collections concurrency portable-class-library
将我的代码移动到可移植框架中,我发现可移植类库(PCL)中根本没有可用的并发集合,这个discussion很好地解释了它。
但我仍然需要 并发字典 模拟在那里运行。 而且我对读取/包含性能比对修改性能更感兴趣。
【问题讨论】:
标签: .net silverlight collections concurrency portable-class-library
如果你对阅读更感兴趣,你应该使用ReaderWriterLockSlim。
这是一个锁定原语,它允许多个读取器或一个写入器在任何给定时间进入临界区。当字典被多个线程读取时,这将导致几乎没有性能损失,并且仅在线程尝试写入时应用互斥。
您可以这样实现(为了简洁起见,我省略了大多数方法 - 我将 Add 作为编写器示例,将 Contains 作为读者示例):
public class CustomDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly IDictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
public bool Contains(KeyValuePair<TKey, TValue> item)
{
_lock.EnterReadLock();
try
{
return _dictionary.Contains(item);
}
finally
{
_lock.EnterReadLock();
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_lock.EnterWriteLock();
try
{
_dictionary.Add(item);
}
finally
{
_lock.ExitWriteLock();
}
}
}
现在,GetEnumerator 的锁定有点棘手,因为字典实际上会在调用 GetEnumerator 之后被读取,而不是在调用 GetEnumerator 时。
所以你应该自己实现枚举器并像这样应用读锁,以便在枚举集合时 持有锁:
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
_lock.EnterReadLock();
try
{
foreach (var entry in _dictionary)
yield return entry;
}
finally
{
_lock.ExitReadLock();
}
}
【讨论】:
字典集合的巨大好处是快速键访问和快速包含评估。 所有字典都使用不同的复杂算法(查看one),从头开始编写自己的字典非常困难。
所以我尝试重用来自BCL 的原始 ConcurrentDictionary 的源代码(您可以为每个基类使用get source code)。 但是,它非常复杂,使用了几十个私有类和低级函数来管理内存。这不是偶然的,它从一开始就在 PCL 中不可用:)
第二个想法是制作普通字典的包装器并在每个方法调用中添加锁定部分。尽管关键部分并不昂贵,但它会导致性能急剧下降。
所以我以明确的 Dictionary 包装器的联锁实现结束。碰巧的是,普通字典支持从不同线程读取和枚举,只要它不是同时修改的。 因此,在每次更改时,我们都会克隆所有字典。在那段时间读取它的所有线程将继续迭代旧副本。
public class InterlockedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
private Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>();
public TValue this[TKey key]
{
get
{
return _dictionary[key];
}
set
{
ApplyAndChange(dict => dict[key] = value);
}
}
public Dictionary<TKey, TValue>.KeyCollection Keys
{
get
{
return _dictionary.Keys;
}
}
public Dictionary<TKey, TValue>.ValueCollection Values
{
get
{
return _dictionary.Values;
}
}
public int Count
{
get
{
return _dictionary.Count;
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
ApplyAndChange(dict => dict.Add(item.Key, item.Value));
}
public void Clear()
{
_dictionary = new Dictionary<TKey, TValue>();
}
public bool ContainsKey(TKey key)
{
return _dictionary.ContainsKey(key);
}
public void Add(TKey key, TValue value)
{
ApplyAndChange(dict => dict.Add(key, value));
}
public bool Remove(TKey key)
{
bool result = false;
ApplyAndChange(dict => result = dict.Remove(key));
return result;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private void ApplyAndChange(Action<Dictionary<TKey, TValue>> action)
{
Dictionary<TKey, TValue> initialDictionary, updatedDictionary, replacedDictionary;
do
{
initialDictionary = _dictionary;
updatedDictionary = new Dictionary<TKey, TValue>(initialDictionary);
action(updatedDictionary);
} while (Interlocked.CompareExchange(ref _dictionary, updatedDictionary, initialDictionary) != initialDictionary);
}
}
这个实现没有实现 IDictionary 作为很多不需要的成员。但是可以通过为所有非修改方法直接调用底层字典并用ApplyAndChange(dict => ...)包装所有修改方法来轻松完成。
注意- 性能方面,与原来的 Dictionary 相比,此实现的 Set、Add 和 Remove 性能更差,等于 Read 和 Contains 性能。
【讨论】:
set 中,_dictionary 可以在行_dictionary.ContainsKey(key) 和_dictionary[key] = value; 之间更改。此解决方案不是线程安全的。
ReaderWriterLockSlim 是一个更好的解决方案 - 您将获得大致相同的性能,但更不容易出错。无锁代码很容易被破解。