该类是否应该使用数据锁定进行多线程?

时间:2010-08-08 09:18:11

标签: .net multithreading locking

我有一个包含一些数据的类,并且有许多线程使用它:

class MyClass
{
    static Dictionary<Key, Value> MyData;

    static IEnumerable<Data> Data
    {
        get
        {
            return MyData.Values;
        }
    }

    static void Reset()
    {
         MyData = GetMyData();
    }
}

有时(比如一天一次)调用Reset方法。我不想因为性能而添加锁定,但不确定是否一切都能正常运行。

问题是:我应该在这个类中使用任何类型的锁定吗?

3 个答案:

答案 0 :(得分:4)

如果它将由多个线程调用,那么:是。

如果争用很少,Monitorlock语句使用的类型)的开销非常低。创建一个私有Object实例以与锁一起使用,并确保所有访问路径都受到保护。

另一种可能性(在.NET 4上)是ConcurrentDictionary

编辑附加:我注意到Data属性会在IEnumerable的内部内容上返回Dictionary。这意味着当调用其他修改方法时,对字典值的迭代可能正在进行。即使使用内部锁定,您也会遇到并发问题。有两种方法:

  1. 将锁定移动到此类型的调用方。如果它是内部或辅助类型(而不是作为API的一部分公开),这可能是一种可行的方法,但它确保了确保正确锁定的负担更加困难。

  2. 复制值,并返回:

    static IEnumerable<Data> Data {
      get {
          return MyData.Values.ToArray();
      }
    }
    

答案 1 :(得分:1)

我不同意理查德。您似乎以不可变的方式使用Dictionary<,>,从不更改内容。它从GetMyData()方法返回完全填充,只是替换MyData上的引用,同时只暴露不可变的Values属性。即使有人在字典中进行枚举而其他人调用Reset(),该人仍将继续枚举旧字典,而任何新读者都将获得新字典。因此,您的代码完全是线程安全的,这要归功于您使用数据结构就好像它们是不可变的一样。您可能希望将其包装在一个真正的不可变字典中(例如from here)另一方面,如果代码的其他部分有不同的用法我们看不到,那么它将是另一回事

编辑:我刚注意到MyDataData都是私有的(那么为什么还要为额外的属性打扰?),所以你也可能正在使用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");
    }
}