代码之家  ›  专栏  ›  技术社区  ›  Kamarey

这个类应该为多线程使用数据锁定吗?

  •  2
  • Kamarey  · 技术社区  · 14 年前

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

    class MyClass
    {
        static Dictionary<Key, Value> MyData;
    
        static IEnumerable<Data> Data
        {
            get
            {
                return MyData.Values;
            }
        }
    
        static void Reset()
        {
             MyData = GetMyData();
        }
    }
    

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

    3 回复  |  直到 14 年前
        1
  •  1
  •   Community CDub    8 年前

    我不同意理查德的观点。你好像用了 Dictionary<,> 以不变的方式,从不改变内容。它从 GetMyData() Values 财产。即使有人在查字典,而有人在打电话 Reset() ,这个人将继续在旧字典上枚举,而任何新的读者将得到新的字典。因此,您的代码是完全线程安全的,因为您使用的数据结构就像它们是不可变的一样。你可能想把它包装在一个真正的不可变字典中(例如。 from here

    编辑:我刚刚注意到 MyData Data 方法是从另一个线程调用的,那么您就没事了。另一方面,如果您在一个线程上修改集合,同时在另一个线程上读取它,那么您必须执行Richard提到的手动同步。关键是 重置() 方法从来不是问题所在,因为它用新实例替换字典的引用,而不影响旧实例。

        2
  •  4
  •   Richard    14 年前

    如果它将被多个线程调用,那么:yes。

    A Monitor (a lock 如果争用很少,则语句uses)的开销非常低。创建专用 Object

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

    编辑补充:我注意到 Data 属性返回 IEnumerable Dictionary . 这意味着在调用其他修改方法时,可以对字典的值进行迭代。即使使用内部锁定,也会出现并发问题。有两种方法:

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

    2. 制作值的副本,并返回:

      static IEnumerable<Data> Data {
        get {
            return MyData.Values.ToArray();
        }
      }
      
        3
  •  1
  •   Community CDub    8 年前

    对。您应该使代码对于多线程操作是安全的。原因是,无论您是否希望代码在多线程环境中运行,使所有静态成员线程安全都是标准做法。当前代码存在过时问题。这是一个线程可以调用的 Reset Data MyData 作为 volatile .

    更新:

    我所说的过时问题与C和JIT编译器如何优化代码有关。例如,考虑两个线程T A 和T B类 A 电话 MyClass.Data 我的数据 T时参考 更改 我的数据 通过调用引用 复位 我的数据 通过将它们提升到循环之外,将它们缓存在CPU寄存器中,等等 A 可能会错过 如果它在CPU寄存器中缓存引用,或者 B类

    这不仅仅是一些在实践中很少出现的理论问题。实际上,用下面的程序演示是很容易的。确保在版本配置中编译代码,并在不使用调试器的情况下运行它(两者都是再现问题所必需的)。粗略地看一下,这个程序应该在大约1秒后终止,但遗憾的是它没有终止,因为 m_Stop 正在被工作线程缓存,并且从未看到主线程将其值更改为 true 停止 作为 不稳定的 . 你可以看到我的解释 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");
        }
    }