假设我们有一些包含
的奇怪的类ReadOnlyDictionary<string, string> Map {get; private set;}
Update
,在调用时重新设置Map
字典。 从一个线程调用方法Update
并从其他线程获取Map
是不是线程安全?
public class Example
{
public ReadOnlyDictionary<string, string> Map{ get; private set; }
public void Update(IEnumerable<KeyValuePair<string, string>> items)
{
Map = items
.ToLookup(x => x.Key)
.ToDictionary(x => x.Key, x => x.First())
.ToReadOnlyDictionary(); // helper method
}
}
是否有可能会出现我们调用Map getter并返回null或处于错误状态的时刻,因为setter执行不是原子的?
答案 0 :(得分:3)
&#34;线程安全&#34;是一个模糊的术语。维基百科将线程安全代码定义为:
线程安全代码仅以某种方式操作共享数据结构 确保所有线程都能正常运行并完成其设计 没有意外互动的规范
所以我们不能说某段代码是&#34;线程安全&#34;不知道设计规范,包括如何实际使用该代码。
此代码是&#34;线程安全&#34;从某种意义上说,任何线程都无法在损坏或部分状态下观察Example
对象。
引用赋值是原子的,因此读取Map
的线程只能读取它的一个或多个,它不能观察到Map
的部分或中间状态。因为Map
的键和值都是不可变的(字符串) - 整个对象是不可变的,并且在从Map
属性读取后可以安全地被多个线程使用。
然而,做这样的事情:
var example = GetExample();
if (example.Map.ContainsKey("key")) {
var key = example.Map["key"];
}
当然不是线程安全的,因为Map
对象作为一个整体,可能在检查和从字典读取之间发生了变化,其方式是旧版本包含&#34; key&#34;关键,但新版本没有。另一方面这样做:
var example = GetExample();
var map = example.Map;
if (map.ContainsKey("key")) {
var key = map["key"];
}
很好。任何不依赖于example.Map
在读取之间保持相同的代码应该是&#34;安全&#34;。
所以你必须确保给定的代码是&#34;线程安全&#34;在你的特定用例中。
请注意,使用ConcurrentDictionary
在这里绝对没用。由于您的字典是只读的 - 从多个线程中读取它是安全的。
更新:评论中提出的有效点是ReadOnlyDictionary
本身并不能保证不修改基础数据,因为ReadOnlyDictionary
只是常规可变Dictionary
的包装。 / p>
但是,在这种情况下,您可以从非共享字典实例(从ReadOnlyDictionary
收到)在Update
方法中创建ToDictionary
,除了将其包含在内外,它不会用于其他任何内容ReadOnlyDictionary
。这是一种安全的用法,因为没有代码可以访问除ReadOnlyDictionary
本身之外的可变基础字典,并且只读字典会阻止其修改。只要没有写入,即使从常规Dictionary
读取也是线程安全的。
答案 1 :(得分:0)
因为setter执行不是原子的
此代码的安全性与原子性无关(读/写引用是.NET中的原子操作,除非你搞砸了对齐)。
因此,此代码中唯一的问题是内存重新排序。可能会发生另一个线程将获得对部分构造的对象的引用。
但它可能只发生在具有弱内存模型的硬件上(“弱”意味着允许大量重新排序并且没有太多限制)。
为了防止这种情况,您只需通过Volatile.Write
发布此引用,对于x86和x86_64体系结构为nop
,为ARM和其他提供一种内存屏障。
答案 2 :(得分:0)
为了使您的代码更简单,更容易出错,您可以使用 ImmutableDictionary :
public class Example
{
public IReadOnlyDictionary<string, string> Map{ get; private set; } =
ImmutableDictionary.Create<string, string>();
public void Update(IEnumerable<KeyValuePair<string, string>> items)
{
Map = items
.ToImmutableDictionary();
}
}
ImmutableDictionary 保证是线程安全的,所以没有办法打破它。通常 Dictionary 不是线程安全的,但是在您的实现中不应该存在任何线程安全问题,但这需要这种特殊用法。
ImmutableDictionary 可在此处下载https://www.nuget.org/packages/System.Collections.Immutable/
另一个线程安全字典是 ConcurrentDictionary 。使用它可能不一定提供额外的成本,因为读取操作是无锁的。文档说明:
字典上的读取操作以无锁方式执行。
所以这两个词典在任何情况下都是线程安全的(在字典API级别上),而 Dictionary 在某些类似于你的实现中是线程安全的。
答案 3 :(得分:0)
Concurrent Collections =线程安全:)
我认为你应该使用Concurrent集合。并发泛型对我来说很有意义。