我有一个包含一些数据的类,并且有许多线程使用它:
class MyClass
{
static Dictionary<Key, Value> MyData;
static IEnumerable<Data> Data
{
get
{
return MyData.Values;
}
}
static void Reset()
{
MyData = GetMyData();
}
}
有时(比如一天一次)调用Reset方法。我不想因为性能而添加锁定,但不确定是否一切都能正常运行。
问题是:我应该在这个类中使用任何类型的锁定吗?
答案 0 :(得分:4)
如果它将由多个线程调用,那么:是。
如果争用很少,Monitor
(lock
语句使用的类型)的开销非常低。创建一个私有Object
实例以与锁一起使用,并确保所有访问路径都受到保护。
另一种可能性(在.NET 4上)是ConcurrentDictionary
编辑附加:我注意到Data
属性会在IEnumerable
的内部内容上返回Dictionary
。这意味着当调用其他修改方法时,对字典值的迭代可能正在进行。即使使用内部锁定,您也会遇到并发问题。有两种方法:
将锁定移动到此类型的调用方。如果它是内部或辅助类型(而不是作为API的一部分公开),这可能是一种可行的方法,但它确保了确保正确锁定的负担更加困难。
复制值,并返回:
static IEnumerable<Data> Data {
get {
return MyData.Values.ToArray();
}
}
答案 1 :(得分:1)
我不同意理查德。您似乎以不可变的方式使用Dictionary<,>
,从不更改内容。它从GetMyData()
方法返回完全填充,只是替换MyData上的引用,同时只暴露不可变的Values
属性。即使有人在字典中进行枚举而其他人调用Reset()
,该人仍将继续枚举旧字典,而任何新读者都将获得新字典。因此,您的代码完全是线程安全的,这要归功于您使用数据结构就好像它们是不可变的一样。您可能希望将其包装在一个真正的不可变字典中(例如from here)另一方面,如果代码的其他部分有不同的用法我们看不到,那么它将是另一回事
编辑:我刚注意到MyData
和Data
都是私有的(那么为什么还要为额外的属性打扰?),所以你也可能正在使用MyData来修改字典。在这种情况下,只要所有字典读/写都发生在同一个线程上,但Reset()
方法是从不同的线程调用的,那么你没关系。另一方面,如果您在另一个线程上从一个线程读取时修改集合,那么您必须执行Richard提到的手动同步。关键是Reset()
方法从来不是问题,因为它用新实例替换字典的引用而不影响旧实例。
答案 2 :(得分:1)
是。您应该使代码对多线程操作安全。原因是因为标准做法是使所有静态成员都是线程安全的,无论您希望代码是否在多线程环境中运行。您当前的代码存在陈旧问题。这是一个线程可能会调用Reset
,但调用Data
的其他线程可能永远不会看到更改。将MyData
标记为volatile
即可轻松解决此问题。
<强>更新强>
我所谈论的陈旧问题与C#和JIT编译器如何优化代码有关。例如,考虑两个线程T A 和T B 。 T A 调用MyClass.Data
因此读取MyData
引用,而T B 通过调用MyData
更改Reset
引用。编译器可以自由地重新排序MyData
的读取和写入,方法是将它们提升到循环外部,将它们缓存在CPU寄存器中等等。这意味着T A 可能会错过更改在MyData
中,如果它将引用缓存在CPU寄存器中,或者如果T B 没有立即将其写入主存储器。
这不仅仅是一些在实践中很少发生的理论问题。事实上,使用以下程序很容易演示。确保在Release配置中编译代码并在没有调试器的情况下运行它们(两者都必须重现问题)。粗略一瞥表明这个程序应该在大约1秒后终止,但是它不会因为m_Stop
被工作线程缓存而从未看到主线程将其值更改为true
。简单的修复方法是将m_Stop
标记为volatile
。您可以查看我的解释here,了解有关记忆障碍概念和指令重新排序的信息。
public class Program
{
private static bool m_Stop = false;
public static void Main(string[] args)
{
var thread = new Thread(
() =>
{
int i = 0;
Console.WriteLine("begin");
while (!m_Stop)
{
i++;
}
Console.WriteLine("end");
});
thread.Start();
Thread.Sleep(1000);
m_Stop = true;
Console.WriteLine("exit");
}
}