当前位置:首页>网络学院>程序开发>c#教程>文章内容

c#线程同步中的lock,monitor,同步事件和等待句柄以及mutex的介绍

[ 来源:http://www.it55.com | 作者: | 时间:2007-12-13 | 收藏 | 推荐 ] 【

     
   最近由于在准备Collection对象培训的PPT,因为涉及到SyncRoot的属性的讲解,所以对怎样在多线程应用程序中同步资源访问做了个总结:
  对于引用类型和非线程安全的资源的同步处理,有四种相关处理:lock关键字,监视器(Monitor), 同步事件和等待句柄, mutex类。
  Lock关键字
   本人愚钝,在以前编程中遇到lock的问题总是使用lock(this)一锁了之,出问题后翻看MSDN突然发现下面几行字:通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:如果实例可以被公共访问,将出现 lock (this) 问题。如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。来看看lock(this)的问题:如果有一个类Class1,该类有一个方法用lock(this)来实现互斥:
  public void Method2()
   {
   lock (this)
   {
   System.Windows.Forms.MessageBox.Show("Method2 End");
   }
   }
   如果在同一个Class1的实例中,该Method2能够互斥的执行。但是如果是2个Class1的实例分别来执行Method2,是没有互斥效果的。因为这里的lock,只是对当前的实例对象进行了加锁。
   Lock(typeof(MyType))锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议(原文请参考:http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/SDaskgui06032003.mspx?mfr=true)不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。
   锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在.NET中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。到此,微软给出了个lock的建议用法:锁定一个私有的static 成员变量。
   .NET在一些集合类中(比如ArrayList,HashTable,Queue,Stack)已经提供了一个供lock使用的对象SyncRoot,用Reflector工具查看了SyncRoot属性的代码,在Array中,该属性只有一句话:return this,这样和lock array的当前实例是一样的。ArrayList中的SyncRoot有所不同 get
   {
   if (this._syncRoot == null)
   {
   Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
   }
   return this._syncRoot;
   其中Interlocked类是专门为多个线程共享的变量提供原子操作(如果你想锁定的对象是基本数据类型,那么请使用这个类),CompareExchange方法将当前syncRoot和null做比较,如果相等,就替换成new object(),这样做是为了保证多个线程在使用syncRoot时是线程安全的。集合类中还有一个方法是和同步相关的:Synchronized,该方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock来进行了同步处理,比如Add方法: public override void Add(object key, object value)
  {
   lock (this._table.SyncRoot)
   {
   this._table.Add(key, value);
   }
  }
   这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合: Queue myCollection = new Queue();
   lock(myCollection.SyncRoot) {
   foreach (Object item in myCollection) {
   // Insert your code here.
   }
   }
  Monitor类
  该类功效和lock类似: System.Object obj = (System.Object)x;
  System.Threading.Monitor.Enter(obj);
  try
  {
   DoSomething();
  }
  finally
  {
   System.Threading.Monitor.Exit(obj);
  }
   lock关键字比Monitor简洁,其实lock就是对Monitor的Enter和Exit的一个封装。另外Monitor还有几个常用的方法:TryEnter能够有效的决绝长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。Pulse以及PulseAll还有Wait方法是成对使用的,它们能让你更精确的控制线程之间的并发,MSDN关于这3个方法的解释很含糊,有必要用一个具体的例子来说明一下:
  using System.Threading;
  public class Program {
   static object ball = new object();
   public static void Main() {
   Thread threadPing = new Thread( ThreadPingProc );
   Thread threadPong = new Thread( ThreadPongProc );
   threadPing.Start(); threadPong.Start();
   }

(编辑:IT资讯之家 www.it55.com

返回顶部
共3页: 上一页 1 [2] [3] 下一页  

网友评论

[以下评论为网友观点,不代表本站。请自觉遵守互联网相关政策法规,所有连带责任均有评论者自负。]
[不超过250字]