C#多线程编程之lock语句应注意的问题

转自:http://hi.baidu.com/oohacker/blog/item/6004e6fb712feb254e4aea24.html

有关C#锁语句的讨论网上说得很多, 但绝大多数只说了了怎么用, 对于lock的注意事项却很少, 尤其是关于lock(this), lock(typeof(ClassName))以及lock("thisLock")的讨论更是凤毛麟角, 就连MSDN中对于前面提及的三种lock可能导致的问题也是一笔带过(http://msdn.microsoft.com/zh-cn/worldwide/c5kehkcz.aspx)。百度和Google了许久,终于收集到相关资料,今天把它写下来,与大家一起分享。

    MSDN上指出,使用lock时不注意可能导致以下问题:

    1). 如果实例可以被公共访问,将出现 lock (this) 问题。

    2). 如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

    3). 由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock(“myLock”) 问题。

    下面来讨论这些问题:

     首先看lock(this)问题,如果一个类是公有的时,应该避免在基方法或属性中使用lock(this)语句,因为如果有其他人使用你的组件,它并不了解你的组件内部是否使用了锁,如果使用了而使用者又在类外部对类实例尝试加锁,则可能导致一个死锁,下面是我从Google(原地址:http://www.toolazy.me.uk/template.php?content=lock(this)_causes_deadlocks.xml,原程序中由于将主线程睡眠,实际操作系统会自动切换可用线程,因而未能体现出死锁的效果)找到的修改后的例子:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Test
{
class InternalClass
{
    public void TryLockThis()
    {
      Thread t = new Thread(ThreadFunction);
      t.Start();
    }

    private void ThreadFunction()
    {
      Thread.Sleep(3000); // 延迟,等待外部对对象实例加锁
      Console.WriteLine("尝试通过lock(this)加锁下面的代码块...");

      while (true)
      {
        lock (this)
        {
          Console.WriteLine("执行内部锁,1秒后继续...");
          Thread.Sleep(1000);
          Console.WriteLine("内部锁完成...");
        }
      }
    }
}


class ClassMain
{
    private InternalClass theClass = new InternalClass();

    public ClassMain()
    {
      theClass.TryLockThis();
      Console.WriteLine("在执行内部锁之前对对象进行加锁...");
      lock (theClass) // 如果注释掉这句,ThreadFunction()中的lock将执行成功
      {
        Console.WriteLine("对象被锁定, 在这里我们获得了一个死锁...");

        while (true) { }
      }
    }

    [STAThread]
    static void Main(string[] args)
    {
      ClassMain cm = new ClassMain();

      Console.WriteLine("Press Enter to exit");
      Console.ReadLine();
    }
}
}

可以看到上述的程序会导致一个死锁,程序的执行结果是这样的:

在执行内部锁之前对对象进行加锁...
对象被锁定, 在这里我们获得了一个死锁...
尝试通过lock(this)加锁下面的代码块...

因此,应尽量避免甚至拒绝使用lock(this)这样的语句。

同样的,lock(typeof(ClassName))也有类似的问题,由于typeof语句返回的是一个类的类型实例,对于一个类来说只有一 个,如果在类的方法或实例方法中使用了typeof(className)这样的语句,而在类的外部又尝试对类进行加锁,同样可能导致死锁。

关于lock("thisLock")的问题,是由.Net的字符串处理模式导致的。
[MSDN]
公共语言运行库通过维护一个表来存放字符串,该表称为拘留池,它包含程序中以编程方式声明或创建的每个唯一的字符串的一个引用。因此,具有特定值的字符串的实例在系统中只有一个。
例如,如果将同一字符串分配给几个变量,运行库就会从拘留池中检索对该字符串的相同引用,并将它分配给各个变量。

由于这个原因,假如你lock的字符串对象在拘留池中(通常是编译时显式声明的用引号引起来的字符 串),那么,这个对象也有可能被分配到另外一个变量,如果这个变量也使用了lock,那么,两个lock语句表面上lock的不同对象,但实质上却是同一 个对象,这将造成不必要的阻塞,甚至可能造成死锁。且不亦发现问题。

从下面的程序中可以看出一个问题:
      string a = "String Example";
      string b = "String Example";
      string c = (new StringBuilder()).Append("String Example").ToString();
      Console.WriteLine("a==b? {0}", object.ReferenceEquals(a, b));
      Console.WriteLine("a==c? {0}", object.ReferenceEquals(a, c));

上面程序执行的结果是:
    a==b? True
    a==c? False

从上面可以看出,a和b指向的是同一个引用,而lock正是通过引用来区分并加锁临界代码段的。也就是说,如果在我们一程序的一个部分中使用了 lock("thisLock")进行加锁,而在程序的另一个位置同样也使用lock("thisLock")进行加锁,则极有可能导致一个死锁,因而是 很危险的。实际上,不只是lock("thisLock")这样的语句,在lock中使用string类型的引用都有可能导致死锁。

   上述就是C#中应避免使用lock(this), lock(typeof(className)), lock("thisLock")的原因。

 
原文地址:https://www.cnblogs.com/mxw09/p/1787116.html