有时候你会觉得上面介绍的方法好像不够用,对,我们解决了代码和资源的同步问题,解决了多线程自动化管理和定时触发的问题,但是如何控制多个线程相互之间的联系呢?例如我要到餐厅吃饭,在吃饭之前我先得等待厨师把饭菜做好,之后我开始吃饭,吃完我还得付款,付款方式可以是现金,也可以是信用卡,付款之后我才能离开。分析一下这个过程,我吃饭可以看作是主线程,厨师做饭又是一个线程,服务员用信用卡收款和收现金可以看作另外两个线程,大家可以很清楚地看到其中的关系
——
我吃饭必须等待厨师做饭,然后等待两个收款线程之中任意一个的完成,然后我吃饭这个线程可以执行离开这个步骤,于是我吃饭才算结束了。事实上,现实中有着比这更复杂的联系,我们怎样才能很好地控制它们而不产生冲突和重复呢?
这种情况下,我们需要用到互斥对象,即 System.Threading 命名空间中的 Mutex 类。大家一定坐过出租车吧,事实上我们可以把 Mutex 看作一个出租车,那么乘客就是线程了,乘客首先得等车,然后上车,最后下车,当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与 Mutex 对象的关系也正是如此,线程使用 Mutex.WaitOne() 方法等待 Mutex 对象被释放,如果它等待的 Mutex 对象被释放了,它就自动拥有这个对象,直到它调用 Mutex.ReleaseMutex() 方法释放这个对象,而在此期间,其他想要获取这个 Mutex 对象的线程都只有等待。
下面这个例子使用了 Mutex 对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个 Mutex 对象相关联的。其中还用到 AutoResetEvent 类的对象,如同上面提到的 ManualResetEvent 对象一样,大家可以把它简单地理解为一个信号灯,使用 AutoResetEvent.Set() 方法可以设置它为有信号状态,而使用 AutoResetEvent.Reset() 方法把它设置为无信号状态。这里用它的有信号状态来表示一个线程的结束。
这种情况下,我们需要用到互斥对象,即 System.Threading 命名空间中的 Mutex 类。大家一定坐过出租车吧,事实上我们可以把 Mutex 看作一个出租车,那么乘客就是线程了,乘客首先得等车,然后上车,最后下车,当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与 Mutex 对象的关系也正是如此,线程使用 Mutex.WaitOne() 方法等待 Mutex 对象被释放,如果它等待的 Mutex 对象被释放了,它就自动拥有这个对象,直到它调用 Mutex.ReleaseMutex() 方法释放这个对象,而在此期间,其他想要获取这个 Mutex 对象的线程都只有等待。
下面这个例子使用了 Mutex 对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个 Mutex 对象相关联的。其中还用到 AutoResetEvent 类的对象,如同上面提到的 ManualResetEvent 对象一样,大家可以把它简单地理解为一个信号灯,使用 AutoResetEvent.Set() 方法可以设置它为有信号状态,而使用 AutoResetEvent.Reset() 方法把它设置为无信号状态。这里用它的有信号状态来表示一个线程的结束。
// Mutex.cs using System; using System.Threading; public class MutexSample { static Mutex gM1; static Mutex gM2; const int ITERS = 100; static AutoResetEvent Event1 = new AutoResetEvent(false); static AutoResetEvent Event2 = new AutoResetEvent(false); static AutoResetEvent Event3 = new AutoResetEvent(false); static AutoResetEvent Event4 = new AutoResetEvent(false); public static void Main(String[] args) { Console.WriteLine("Mutex Sample ..."); // 创建一个 Mutex 对象,并且命名为 MyMutex gM1 = new Mutex(true,"MyMutex"); // 创建一个未命名的 Mutex 对象 . gM2 = new Mutex(true); Console.WriteLine(" - Main Owns gM1 and gM2"); AutoRe setEvent[] evs = new AutoResetEvent[4]; evs[0] = Event1; file:// 为后面的线程 t1,t2,t3,t4 定义 AutoResetEvent 对象 evs[1] = Event2; evs[2] = Event3; evs[3] = Event4; MutexSample tm = new MutexSample( ); Thread t1 = new Thread(new ThreadStart(t m.t1Start)); Thread t2 = new Thread(new ThreadStart(tm.t2Start)); Thread t3 = new Thread(new ThreadStart(tm.t3Start)); Thread t4 = new Thread(new ThreadStart(tm.t4Start)); t1.Start( );// 使用 Mutex.WaitAll() 方法等待一个 Mutex 数组中的对象全部被释放 t2.Start( );// 使用 Mutex.WaitOne() 方法等待 gM1 的释放 t3.Start( );// 使用 Mutex.WaitAny() 方法等待一个 Mutex 数组中任意一个对象被释放 t4.Start( );// 使用 Mutex.WaitOne() 方法等待 gM2 的释放 Thread.Sleep(2000); Console.WriteLine(" - Main releases gM1"); gM1.ReleaseMutex( ); file:// 线程 t2,t3 结束条件满足 Thread.Sleep(1000); Console.WriteLine(" - Main releases gM2"); gM2.ReleaseMutex( ); file:// 线程 t1,t4 结束条件满足 // 等待所有四个线程结束 WaitHandle.WaitAll(evs); Console.WriteLine("... Mutex Sample"); Console.ReadLine(); } public void t1Start( ) { Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])"); Mutex[] gMs = new Mutex[2]; gMs[0] = gM1;// 创建一个 Mutex 数组作为 Mutex.WaitAll() 方法的参数 gMs[1] = gM2; Mutex.WaitAll(gMs);// 等待 gM1 和 gM2 都被释放 Thread.Sleep(2000); Conso le.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied"); Event1.Set( ); file:// 线程结束,将 Event1 设置为有信号状态 } public void t2Start( ) { Console.WriteLine("t2Start started, gM1.WaitOne( )"); gM1.WaitOne( );// 等待 gM1 的释放 Console.WriteLi ne("t2Start finished, gM1.WaitOne( ) satisfied"); Event2.Set( );// 线程结束,将 Event2 设置为有信号状态 } public void t3Start( ) { Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])"); Mutex[] gMs = new Mutex[2]; gMs[0] = gM1;// 创建一个 Mutex 数组作为 Mutex.WaitAny() 方法的参数 gMs[1] = gM2; Mutex.WaitAny(gMs);// 等待数组中任意一个 Mutex 对象被释放 Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])"); Event3.Set( );// 线程结束,将 Event3 设置为有信号状态 } public void t4Start( ) { Console.WriteLine("t4Start started, gM2.WaitOne( )"); gM2.WaitOne( );// 等待 gM2 被释放 Console.WriteLine("t4Start finished, gM2.WaitOne( )"); Event4.Set( );// 线程结束,将 Event4 设置为有信号状态 } } |
下面是该程序的执行结果:
从执行结果可以很清楚地看到,线程 t2,t3 的运行是以 gM1 的释放为条件的,而 t4 在 gM2 释放后开始执行, t1 则在 gM1 和 gM2 都被释放了之后才执行。 Main() 函数最后,使用 WaitHandle 等待所有的 AutoResetEvent 对象的信号,这些对象的信号代表相应线程的结束。