NET多线程同步和互斥机制初探

Posted on 2005-08-24 10:02  橙子  阅读(2596)  评论(1编辑  收藏  举报

多线程开发中一个最重要的问题就是公用资源的同步和互斥问题。.NET框架库为同步和互斥问题提供了强大的支持。

要弄清.NET处理同步和互斥问题的机制,必须先简单回忆一下线程的一些概念。我们都知道,线程有三种状态:Executing、Ready、Suspended。(在不同的《操作系统》中文教材里,这三个概念的译法比较混乱。Executing被译成“运行”或“执行”,Ready被译成“就绪”或“等待”,Suspended被译成“挂起”或“阻塞”。如果你不知道它们的英文概念是什么,你很容易被这些中文概念搞混。甚至连MSDN也有这种现像,有的文章用的是“等待”和“阻塞”,而有的用的是“就绪”和“挂起”,一度把我搞得满头雾水。所以在这里我都用英文表示。)Executing线程是得到资源正在运行的线程。Ready线程和Suspended是没有资源所以没有运行的线程,两者的区别是Ready线程在得到资源后可以立即运行,而Suspended必须先被激活为Ready线程后才能得到资源运行。这三种线程分别放在三个先进先出的队列中。

.NET框架库的System.Threading.Monitor类封装了处理公用资源同步和互斥的方法。它提供了两套机制。

Enter方法和Exit方法

第一套是采用Enter方法和Exit方法。它们都只有一个重载,带一个object类型的参数obj。

Enter方法给obj类型加了一个互斥锁。当线程A对obj调用了Enter方法后,若线程B再对obj调用Enter方法,则线程B会被放入Ready队列。当然,也可以使用TryEnter方法,该方法和Enter方法功能大致相同,区别在于TryEnter会返回一个Bool型的返回值,若obj被锁,它并不会把线程放入Ready队列,而是返回一个false值。

与Enter方法相对的是Exit方法,它是用于解除加在obj上的互斥锁。当Exit方法被调用,则obj将会分配给Ready队列中的下一个线程,这个线程就会对分配到的obj对象加锁并进入Executing状态。

关于Enter方法和Exit方法有几点要说明:

1、Enter方法和Exit方法都是针对对象而不是变量加减锁的。若线程调用了Monitor.Enter(a),然后让变量a引用了其它对象,则再调用Monitor.Exit(a)是不能解锁的。同样,也不能将NULL传递给Enter方法和Exit方法。

2、Enter方法和Exit方法的参数都是object类型的,不能将值类型变量作为参数传递给它们。因为传递的值类型变量经装箱后会生成不同的对象。

3、一个线程可以在调用Exit方法之前多次对同一个对象调用Enter方法而不会被放入到Ready队列中,但必须调用相同多次的Exit方法才能对该对象彻底解锁。

4、如果在同一个方法里的对同一对象调用Enter方法和Exit方法,可以采用lock语句。代码:
    lock(obj)
    {
        ...
    }
    将被编译器自动转换为:
    try
    {
        Monitor.Enter(obj);
        ...
    }
    finally
    {
        Monitor.Exit(obj);
    }
    因此使用lock语句和使用Enter方法和Exit方法是等效的。

5、由于Enter方法和Exit方法使用的是互斥锁,如果同时对两个对象使用Enter方法和Exit方法,可能会造成死锁,这一定要注意。

Enter方法和Exit方法可以解决绝大多数的线程同步和互斥问题。但有时我们需要让一个线程主动地释放资源停下来等其它的线程,这时Enter方法和Exit方法就不够用了,因此.NET提供了第二套实现线程同步和互斥的机制。

Wait方法和Pulse方法

第二套机制更复杂些,它是在Enter方法和Exit方法的基础上使用Wait方法和Pulse方法。调用Wait方法和Pulse方法的线程必须拥有obj上的锁,也就是说必须先调用Enter方法且沿未调用Exit方法。

Wait方法是让当前线程释放加在obj上的互斥锁,当前线程进入Suspended队列。如果Wait方法不带TimeOut参数,则在被激活并重新运行之前会无限等待,并永远返回True值。如果Wait方法带了TimeOut参数,则若在TimeOut规定的时间内运行,则返回True值,若过了TimeOut规定的时间线程同样会继续运行,但会返回False值。

Pulse方法既不对当前线程产生影响,也不会对obj上的互斥锁产生影响,它只是从Suspended线程队列中取出一个线程放入Ready线程队列中(也就是将这个线程激活)。在调用 Pulse方法的线程释放锁后,Ready队列中的下一个线程(不一定是调用Pulse方法时激活的那个线程)将获得该锁从而进入Executing状态。如果要将Suspended线程队列中的所有线程激活,可以使用PulseAll方法。

Copyright © 2024 橙子
Powered by .NET 8.0 on Kubernetes