线程:锁定在引擎盖下

时间:2010-08-11 00:17:48

标签: c# multithreading locking

当我们使用锁定对象时会发生什么?我知道它运行时使用了monitor.Enter和Exit方法。但真正发生在引擎盖下的是什么? 为什么只有用于锁定的引用类型? 即使用于完成锁定的对象发生了变化,为什么还能提供线程安全性?

在当前样本中,我们正在修改用于锁定目的的对象。理想情况下,这不是一种优选的方法,最佳实践是使用专用的私有范围变量。

static List<string> stringList = new List<string>();
    static void AddItems(object o)
    {
        for (int i = 0; i < 100; i++)
        {
            lock (stringList)
            {
                Thread.Sleep(20);
                stringList.Add(string.Format("Thread-{0},No-{1}", Thread.CurrentThread.ManagedThreadId, i));
            }
        }
        string[] listArray = null;

        lock(stringList)
        listArray = stringList.ToArray();

        foreach (string s in listArray)
        {
            Console.WriteLine(s);
        }
    }

2 个答案:

答案 0 :(得分:6)

引擎盖下发生的事情是这样的:

  • 想象一下,object类型中有一个隐藏字段。
  • Monitor.Enter()Monitor.Exit()使用该字段进行相互沟通。
  • 每个引用类型都从object继承该字段。

当然,该字段的类型是特殊的:它是一个以线程安全的方式工作的同步锁。实际上,当然,它实际上并不是CLR意义上的字段,而是CLR的一个特殊功能,它在每个对象的内存中使用一块内存来实现同步锁定。 (确切的实现在MSDN杂志的“Safe Thread Synchronization”中有描述。)

为什么它仍然提供线程安全?我认为你的意思是:为什么它不会破坏线程安全的对象的线程安全?答案很简单:因为你可以拥有部分线程安全且部分不安全的对象。你可以拥有一个带有两个方法的对象,并且使用其中一个方法是线程安全的,而另一个方法则不是。无论对象的其余部分是什么,Monitor.Enter()都是线程安全的。

为什么只有用于锁定的引用类型?因为只有引用类型实际上在它们的内存块中具有这种特殊的魔力。值类型实际上只是值本身:在int的情况下是32位整数;在自定义结构的情况下所有字段的串联。您可以将值类型传递给Monitor.Enter(),它不会抱怨,但它不起作用,因为值类型将装箱 - 即包装到参考对象中类型。当您致电Monitor.Exit()时,它会再次装箱 ,因此会尝试释放不同对象参考上的锁。

关于您的代码示例:我认为它没有任何问题。您对stringList变量的所有访问都包含在lock中,除了在初始化期间,您永远不会分配给stringList字段。这没有什么可以出错的;它是线程安全的。 (当然,如果某些其他代码在没有锁定的情况下访问该字段,可能会出错。如果您要将该字段公开,则很有可能会意外地发生这种情况。没有必要除非你真的不能确保你不能控制的代码不能访问该变量,否则只能使用本地范围的变量进行锁定。)

答案 1 :(得分:4)

  

但是真正发生了什么   罩?

有关详细说明,请参阅this MSDN article

实质上,每个分配的CLR对象都有一个包含同步块索引的关联字段。该索引指向CLR维护的同步块池。同步块保存与在同步期间使用的关键部分相同的信息。最初,对象的同步块索引没有意义(未初始化)。但是,当您锁定对象时,它会获得池中的有效索引。

  

为什么只使用引用类型   锁定?

您需要一个引用类型,因为值类型没有关联的同步块索引字段(开销较小)。

  

即使用于的对象   完成锁定改变了,   它怎么还提供线程   安全

锁定CLR对象然后对其进行修改类似于具有CRITICAL_SECTION成员的C ++对象,该成员在修改同一对象时用于锁定。那里没有线程安全问题。

  

在目前的样本中,我们是   修改用于的对象   锁定的目的。理想情况下,这是   不是这样做的首选方式   最佳实践是使用专用   私有范围的变量。

正确,这种情况也在文章中描述。如果您没有使用完全控制所属类的私有范围变量,那么当两个单独的类决定对同一引用对象lock时,您可能遇到死锁问题(例如,如果由于某种原因, stringList被传递给另一个类,然后在其上决定lock。这是不太可能的,但是如果你使用一个永远不会被传递的严格控制的私有范围变量,你将完全避免这种死锁。