.NET中几种基本的线程同步方法

每个程序员学编程的时候都是从单线程的程序入手的,等到了具有比较多的编程实践经验后才开始接触到多线程编程,多线程的技术在程序使用上带来新的友 好体验的同是也带来了一系列的问题,其中最大的一个问题就是“同步/死锁”。 在C#中提供了多种实现同步的类和方法,下面就分别对每种方式作一个说明。

      首先我把同实现同步的所有方式分了几个类,第一类我称作线程本身的同步,就是指线程本身发起的同步行为。这 类里的代表就是Thread.Join和ProcessInfo.WaitForExit。其实WaitForExit是等待进程执行完成的操作,严格来 说不属于线程同步操作,之所以我把这个方法放到这一类里面,是因为它的行为和Thread.Join的方式基本上是一模一样的。另外需要说明的是 Thread.Join这个方法,MSDN上的解释是“阻塞调用线程,直到某个线程终止时为止”,也许这句话是从英文直接翻译过来的,在英语里有上下文语 境,可以知道“调用线程”和“某个线程”到底指的是哪一个,可是到翻译成中文后,这些信息就不清楚了。网上很多人都在说这里描述得很模糊。我一开始也被弄 晕了,后来自己写了个demo程序亲自验证了一下就弄清楚了。所谓的“调用线程”不是指.Join()方法前面的那个线程引用,而是指当前的代码正在运行 的线程,“某个线程”才是指.Join()方法前面的那个线程引用。所以Join方法在这里的作用是指阻塞当前代码运行的线程,直到发起Join操作的那 个线程引用所代表的线程退出后才停止阻塞(使用无限期阻塞的方式的时候)。明白了这里后就里就有一个问题了,当我们调用一个当前代码所在的线程的引用上的 Join方法时,会发生什么现象呢?比如说在任何地方调用Thread.CurrentThread.Join方法。结果会是两种,当我们调用的是有超时 限制的方法的时候,感觉就会像调用Sleep方法一样,当前代码执行的线程会阻塞给定的时间后停止阻塞(我曾经遇到过总是喜欢使用这种方式的人,但现在我 也说不清楚和Sleep方式到底有什么具体的区别,既然MS提供了一个Sleep方法专门来实现线程的休眠我们还是老老实实的用Sleep方法吧,另外从 逻辑上来讲,一个线程等待自己);第二种结果是永久性阻塞。这也可以算是我们遇到的第一种死锁的情况吧,这种还是比较容易避免的,就算是遇到了也容易解决。

      第二类我称作原子操作类别,这一类的特点是所有操作都被当作是原子操作,一旦开始执行除非是相应的代码块运行结束,操作系统是不会打断该线程的执行的。这 一类里面包括了Interlocked,MethodImplAttribute这两个类。其中Interlocked实现的是最基本的变量赋值,比较等 操作的原子同步,一般大家用得比较多,也比较熟悉了,而另一个MethodImplAttribute可以大家就比较陌生了。其实我第一次见到这个 Attribute是在看.Net内部的源代码的时候,当时见到得最多的就是 MethodImpAttribute(MethodImplOptions.InternalCall),也就是内部调用,按MSDN的说法就是调用实 现CLR的基础组件中的方法,有点DllImport的味道。不过这不是我们关心的重点,我所关心的是参数里枚举的另一个 值,MethodImplOptions.Synchronized这个值的作用是告诉CLR当前MethodImpAttribute所描述的方法需要 当作同步块执行,也就是说一次只能有一个线程进入到方法中。直到该方法返回。不过使用这个特性有一个不爽的地方,调式的时候没法跟踪进入到同步块中。

      第三类同步方式我称作对Windows的API包装的方式。 这一类里面有Semaphore,Mutex,ManualResetEvent/AutoResetEvent/EventWaitHandle /WaitHandle,以及同WaitHandle派生的一系列子类型。每一个类的使用方法基本上与Windows API的操作相同,这方面的参考资料网上有一大堆,具体用法MSDN上面都有说明,这里也就不在叙述了。这一类最大的特点就是大多数情况需要在三个不同的 地方执行“设置”,“复位”,“等待”三种操作。当然具体的每个类型都有各自特有的一些方法和特点,每一种类型都有一种特定的应用场景。

简单来说ManualResetEvent/AutoResetEvent/EventWaitHandle/WaitHandle多用于事件通知 模式的同步方式,两个或多个线程通过对事件的完成状态设置值来进行同步,ManualResetEvent在必要的时候需要进行手动复位事件完成状态,而 AutoResetEvent不需要手动设置,当且仅当有一个个线程从阻塞等待中返回时,就自动复位了,也就是说如果有两个或两个以上线程同时在等待 AutoResetEvent事件完成的时候,一次最多只可以有一个等待线程返回。

Semaphore为控制一个具有有限数量用户资源而设计,与操作系统P.V的概念一样,用在限制能够同时访问一个资源的最大次数。当 Semaphore初始化的时候设置的最大同时访问的资源数等于1的时候,其表现出的功能就和ManualResetEvent基本上没有什么区别了。

Mutex在语义上是互斥量,为协调共同对一个共享资源的单独访问而设计的。多数情况下是用于进程级别的同步,比如一个程序只允许在一台机子上执行 一次就可以用这个类来判断,以一个GUID命名一个Mutex,如果之前打开过的话,说明程序已经启动了就无需再次启动,直接退出就行了。当然也可以把 Mutex用作一段代码的同步,使用方法与Semaphore类似。

  这里需要注意的是信号量或事件完成状态的设置放的位子不正确很有可能会导致出现很怪异的错误。

posted on 2009-02-11 19:58  gogogo  阅读(436)  评论(0编辑  收藏  举报

导航