• C#多线程总结


    1.几个重要的概念。

    (1)进程:当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。

    (2)线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

    (3)多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    (4)任何程序在执行时,至少有一个主线程。

    多线程的好处:可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

    多线程的缺点:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 多线程需要协调和管理,所以需要CPU时间跟踪线程; 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题; 线程太多会导致控制太复杂,最终可能造成很多Bug。

    2.在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。

    Thread类有几个重要的方法,如下:

    Start():启动线程;

    Sleep(int):静态方法,暂停当前线程指定的毫秒数;

    Abort():通常使用该方法来终止一个线程;

    Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;

    Resume():恢复被Suspend()方法挂起的线程的执行。 

    一个例子:

    public static int Main(string[] args)
    {
        Console.WriteLine("Thread Start/Stop/Join Sample");
        Thread nThread = new Thread(new ThreadStart(Thread1));
        nThread.Start();
        while (!nThread.IsAlive)
            Thread.Sleep(1);
        nThread.Abort();
        nThread.Join();//Join:阻塞调用线程,直到某个线程终止或经过了指定时间为止。Join代码写在哪,哪个就是调用线程;哪个线程执行了Join方法,则这个线程为某个线程。
        Console.WriteLine();
        Console.WriteLine("Thread1 has finished");
        try
        {
            Console.WriteLine("Try to restart the Thread1 thread");
            nThread.Start();
        }
        catch (ThreadStateException)
        {
            Console.Write("ThreadStateException trying to restart Thread1. ");
            Console.WriteLine("Expected since aborted threads cannot be restarted.");
            Console.ReadLine();
        }
        return 0;
    }
    public static void Thread1()
    {
        while (true)
        {
            Console.WriteLine("Thread1 is running in its own thread.");
        }

    } 

    在Main()函数的While循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程nThread。然后我们试图用Abort()方法终止线程nThread,后面的Join()方法使主线程等待,直到nThread线程结束。之后我们试图将nThread重新启动,但前面的Abort()方法结束线程是不可恢复的,最后程序抛出异常。

    3.生产者和消费者

    每个线程都有自己的资源 ,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因些必须避免这种情况的发生。

    (1)C#提供了关键字lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义:lock(expression) statement_block。expression代表你希望跟踪的对象,通常是对象引用。如果你想保护一个类的实例,一般地使用this;如果你想保护一个静态变量,一般使用类名就可以了。 statement_block就是互斥段的代码,这段代码一个时刻内只可能被一个线程执行。

    (2)当线程公用一个对象时,也会出现和公用代码类似的问题。这种问题就不应该使用lock关键字了,这里需要用Monitor,我们称之为监视器,Monitor提供了使线程共享资源的方案。Monitor可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。Monitor必须和一个具体的对象相关联。当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其他线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。

    对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:

    1>是现在持有锁的线程的引用;

    2>是一个预备队列,队列中保存了已经准备好获取锁的线程;

    3>是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

    当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。 

    下面是如何使用lock和Monitor来实现线程的同步和通讯的例子。这个例子中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并显示。

    首先,定义一个被操作的对象的类Cell,在这个类里,有两个方法:ReadFromCell()和WriteToCell。消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。

    public class Cell
    {
        int cellContents; // Cell对象里边的内容
        bool readerFlag = false// 状态标志,为true时可以读取,为false则正在写入
        public int ReadFromCell()
        {
            lock (this)
            {
                if (!readerFlag)//如果现在不可读取
                {
                    try
                    {
                        //等待WriteToCell方法中调用Monitor.Pulse()方法
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                        Console.WriteLine(e);
                    }
                }
                Console.WriteLine("Consume: {0}", cellContents);
                readerFlag = false;//重置readerFlag标志,表示消费行为已经完成
                Monitor.Pulse(this);//通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
            }
            return cellContents;
        }
        public void WriteToCell(int n)
        {
            lock (this)
            {
                if (readerFlag)
                {
                    try
                    {
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                        //当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                        //当线程在等待状态的时候中止 
                        Console.WriteLine(e);
                    }
                }
                cellContents = n;
                Console.WriteLine("Produce: {0}", cellContents);
                readerFlag = true;
                Monitor.Pulse(this);//通知另外一个线程中正在等待的ReadFromCell()方法
            }
        }
    }
    //下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。
    public class CellProd
    {
        Cell cell; // 被操作的Cell对象
        int quantity = 1// 生产者生产次数,初始化为1 

        public CellProd(Cell box, int request)
        {
            //构造函数
            cell = box;
            quantity = request;
        }
        public void ThreadRun()
        {
            for (int looper = 1; looper <= quantity; looper++)
                cell.WriteToCell(looper); //生产者向操作对象写入信息
        }
    }
    public class CellCons
    {
        Cell cell;
        int quantity = 1;
        public CellCons(Cell box, int request)
        {
            //构造函数
            cell = box;
            quantity = request;
        }
        public void ThreadRun()
        {
            int valReturned;
            for (int looper = 1; looper <= quantity; looper++)
                valReturned = cell.ReadFromCell();//消费者从操作对象中读取信息
        }

    } 

    然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。

    public static void Main(String[] args)
    {
        int result = 0//一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
        Cell cell = new Cell();
        //下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
        CellProd prod = new CellProd(cell, 20);
        CellCons cons = new CellCons(cell, 20);
        Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
        Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
        //生产者线程和消费者线程都已经被创建,但是没有开始执行 
        try
        {
            producer.Start();
            consumer.Start();
            producer.Join();
            consumer.Join();
            Console.ReadLine();
        }
        catch (ThreadStateException e)
        {
            //当线程因为所处状态的原因而不能执行被请求的操作
            Console.WriteLine(e);
            result = 1;
        }
        catch (ThreadInterruptedException e)
        {
            //当线程在等待状态的时候中止
            Console.WriteLine(e);
            result = 1;
        }
        //尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
        Environment.ExitCode = result;

    } 

    在这个例子中,同步是通过等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulese()发出的“脉冲”。

    4.多线程的自动管理(线程池)

    在多线程的程序中,经常会出现两种情况:

    (1)应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应这一般使用ThreadPool(线程池)来解决;

    (2)另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒这一般使用Timer(定时器)来解决;

    ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数。

    将线程安放在线程池里,需使用ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:

    //将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数

    public static bool QueueUserWorkItem(WaitCallback);

    //重载的方法如下,参数object将传递给WaitCallback所代表的方法

    public static bool QueueUserWorkItem(WaitCallback, object);

    ThreadPool类是一个静态类,你不能也不必要生成它的对象。而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是无法取消的。

    在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

    如果应用程序需要对线程进行特定的控制,则不适合使用线程池,需要创建并管理自己的线程。不适合使用线程池的情形包括:

    — 如果需要使一个任务具有特定的优先级。

    — 如果具有可能会长时间运行(并因此阻塞其他任务)的任务。

    — 如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)。

    — 如果需要用永久标识来标识和控制线程,比如想使用专用线程来中止该线程,将其挂起或按名称发现它。 

    用法: 

    首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。例1中,当线程池中所有线程工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行。

    ManualResetEvent对象有几个重要的方法: 

    初始化该对象时,用户可以指定其默认的状态(有信号/无信号);在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

    Reset()方法:将其设置为无信号状态;

    Set()方法:将其设置为有信号状态。

    WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。 

    然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。

    例1:

    public static int Main(string[] args)
    {
        Console.WriteLine("Thread Pool Sample:");
        bool W2K = false;
        int MaxCount = 10;//允许线程池中运行最多10个线程
        
    //新建ManualResetEvent对象并且初始化为无信号状态
        ManualResetEvent eventX = new ManualResetEvent(false);
        Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);
        Alpha oAlpha = new Alpha(MaxCount);
        //创建工作项
        
    //注意初始化oAlpha对象的eventX属性
        oAlpha.eventX = eventX;
        Console.WriteLine("Queue to Thread Pool 0");
        try
        {
            //将工作项装入线程池 
            
    //这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
            ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0));
            W2K = true;
        }
        catch (NotSupportedException)
        {
            Console.WriteLine("These API's may fail when called on a non-Windows 2000 system.");
            W2K = false;
        }
        if (W2K)//如果当前系统支持ThreadPool的方法.
        {
            for (int iItem = 1; iItem < MaxCount; iItem++)
            {
                //插入队列元素
                Console.WriteLine("Queue to Thread Pool {0}", iItem);
                ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(iItem));
            }
            Console.WriteLine("Waiting for Thread Pool to drain");
            //等待事件的完成,即线程调用ManualResetEvent.Set()方法
            eventX.WaitOne(Timeout.Infinite, true);
            //WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
            Console.WriteLine("Thread Pool has been drained (Event fired)");
            Console.WriteLine();
            Console.WriteLine("Load across threads");
            foreach (object o in oAlpha.HashCount.Keys)
                Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);
        }
        Console.ReadLine();
        return 0;

    } 

    //这是用来保存信息的数据结构,SomeState类是一个保存信息的数据结构,它在程序中作为参数被传递给每一个线程,因为你需要把一些有用的信息封装起来提供给线程,而这种方式是非常有效的。
    public class SomeState
    {
        public int Cookie;
        public SomeState(int iCookie)
        {
            Cookie = iCookie;
        }
    }
    public class Alpha
    {
        public Hashtable HashCount;
        public ManualResetEvent eventX;
        public static int iCount = 0;
        public static int iMaxCount = 0;

        public Alpha(int MaxCount)
        {
            HashCount = new Hashtable(MaxCount);
            iMaxCount = MaxCount;
        }
        //线程池里的线程将调用Beta()方法
        public void Beta(Object state)
        {
            //输出当前线程的hash编码值和Cookie的值
            Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(), ((SomeState)state).Cookie);
            Console.WriteLine("HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
            lock (HashCount)
            {
                //如果当前的Hash表中没有当前线程的Hash值,则添加之
                if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
                    HashCount.Add(Thread.CurrentThread.GetHashCode(), 0);
                HashCount[Thread.CurrentThread.GetHashCode()] =
                    ((int)HashCount[Thread.CurrentThread.GetHashCode()]) + 1;
            }
            int iX = 2000;
            Thread.Sleep(iX);
            //Interlocked.Increment()操作是一个原子操作,InterLocked类也是专为多线程程序而存在的,它提供了一些有用的原子操作。原子操作:就是在多线程程序中,如果这个线程调用这个操作修改一个变量,那么其他线程就不能修改这个变量了,这跟lock关键字在本质上是一样的。
            Interlocked.Increment(ref iCount);
            if (iCount == iMaxCount)
            {
                Console.WriteLine();
                Console.WriteLine("Setting eventX ");
                eventX.Set();
            }
        }

    } 

    例2: 

    // 存放要计算的数值的字段
    static double number1 = -1;
    static double number2 = -1;
    public static void Main()
    {
        // 获取线程池的最大线程数和维护的最小空闲线程数
        int maxThreadNum, portThreadNum;
        int minThreadNum;
        ThreadPool.GetMaxThreads(out maxThreadNum, out portThreadNum);
        ThreadPool.GetMinThreads(out minThreadNum, out portThreadNum);
        Console.WriteLine("最大线程数:{0}", maxThreadNum);
        Console.WriteLine("最小空闲线程数:{0}", minThreadNum);
        // 函数变量值
        int x = 256;
        // 启动第一个任务:计算x的8次方
        Console.WriteLine("启动第一个任务:计算{0}的8次方。", x);
        ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), x);
        // 启动第二个任务:计算x的8次方根
        Console.WriteLine("启动第二个任务:计算{0}的8次方根。", x);
        ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), x);
        // 等待,直到两个数值都完成计算
        while (number1 == -1 || number2 == -1) ;

        // 打印计算结果
        Console.WriteLine("{0}的8次方 = {1}", x, number1);
        Console.WriteLine("{0}的8次方根 = {1}", x, number2);
        Console.ReadLine();
    }
    // 启动第一个任务:计算x的8次方
    static void TaskProc1(object o)
    {
        number1 = Math.Pow(Convert.ToDouble(o), 8);
    }
    // 启动第二个任务:计算x的8次方根
    static void TaskProc2(object o)
    {
        number2 = Math.Pow(Convert.ToDouble(o), 1.0 / 8.0);

    } 

    5.多线程自动管理(定时器)

    Timer类:设置一个定时器,定时执行用户指定的函数。

    定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。

    初始化一个Timer对象:

    Timer timer = new Timer(timerDelegate, s,1000, 1000);

    第一个参数:指定了TimerCallback 委托,表示要执行的方法;

    第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;

    第三个参数:延迟时间——计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;

    第四个参数:定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。指定 Timeout.Infinite 可以禁用定期终止。

    Timer.Change()方法:修改定时器的设置。 

    class TimerExampleState
    {
        public int counter = 0;
        public Timer tmr;
    }
    public static void Main()
    {
        TimerExampleState s = new TimerExampleState();
        //创建代理对象TimerCallback,该代理将被定时调用
        TimerCallback timerDelegate = new TimerCallback(CheckStatus);
        //创建一个时间间隔为1s的定时器
        Timer timer = new Timer(timerDelegate, s, 10001000);
        s.tmr = timer;
        //主线程停下来等待Timer对象的终止
        while (s.tmr != null)
            Thread.Sleep(0);
        Console.WriteLine("Timer example done.");
        Console.ReadLine();
    }
    //下面是被定时调用的方法
    static void CheckStatus(Object state)
    {
        TimerExampleState s = (TimerExampleState)state;
        s.counter++;
        Console.WriteLine("{0} Checking Status {1}.", DateTime.Now.TimeOfDay, s.counter);
        if (s.counter == 5)
        {
            //使用Change方法改变了时间间隔
            (s.tmr).Change(100002000);
            Console.WriteLine("changed");
        }
        if (s.counter == 10)
        {
            Console.WriteLine("disposing of timer");
            s.tmr.Dispose();
            s.tmr = null;
        }

    } 

    程序首先创建了一个定时器,它将在创建1秒之后开始每隔1秒调用一次CheckStatus()方法,当调用5次以后,在CheckStatus()方法中修改了时间间隔为2秒,并且指定在10秒后重新开始。当计数达到10次,调用Timer.Dispose()方法删除了timer对象,主线程于是跳出循环,终止程序。

    6.互斥对象

    如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类。

    我们可以把Mutex看作一个出租车,乘客看作线程。乘客首先等车,然后上车,最后下车。当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。

    下面这个例子使用了Mutex对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个Mutex对象相关联的。

    其中还用到AutoResetEvent类的对象,可以把它理解为一个信号灯。这里用它的有信号状态来表示一个线程的结束。

    // AutoResetEvent.Set()方法设置它为有信号状态

    // AutoResetEvent.Reset()方法设置它为无信号状态

    例:

    using System;
    using System.Threading;
    namespace ThreadExample
    {
        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");
                AutoResetEvent[] evs = new AutoResetEvent[4];
                evs[0] = Event1; //为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
                evs[1] = Event2;
                evs[2] = Event3;
                evs[3] = Event4;
                MutexSample tm = new MutexSample();
                Thread t1 = new Thread(new ThreadStart(tm.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(); //线程t2,t3结束条件满足
                Thread.Sleep(1000);
                Console.WriteLine(" - Main releases gM2");
                gM2.ReleaseMutex(); //线程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);
                Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
                Event1.Set(); //线程结束,将Event1设置为有信号状态
                gM1.ReleaseMutex();
                gM2.ReleaseMutex();
            }
            public void t2Start()
            {
                Console.WriteLine("t2Start started, gM1.WaitOne( )");
                gM1.WaitOne();//等待gM1的释放
                Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
                Event2.Set();//线程结束,将Event2设置为有信号状态
                gM1.ReleaseMutex();
            }
            public void t3Start()
            {
                Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
                Mutex[] gMs = new Mutex[2];
                gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
                gMs[1] = gM2;
                int i=Mutex.WaitAny(gMs);//等待数组中任意一个Mutex对象被释放
                Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
                Event3.Set();//线程结束,将Event3设置为有信号状态
                gMs[i].ReleaseMutex();
            }
            public void t4Start()
            {
                Console.WriteLine("t4Start started, gM2.WaitOne( )");
                gM2.WaitOne();//等待gM2被释放
                Console.WriteLine("t4Start finished, gM2.WaitOne( )");
                Event4.Set();//线程结束,将Event4设置为有信号状态
            }
        }

    } 


    参考:http://kb.cnblogs.com/page/42528/

  • 相关阅读:
    React Native区分安卓/iOS平台
    yarn命令使用
    React 源码剖析系列 - 不可思议的 react diff
    dangerouslySetInnerHTMl
    iOS12下APP进入后台后再返回前台连接断开
    AttributedString-富文本字符串
    Bundle创建与使用
    UIButton-详解
    实战项目-百思不得姐-精华
    iOS 抖音个人主页布局开发(简单)
  • 原文地址:https://www.cnblogs.com/sydeveloper/p/2874640.html
Copyright © 2020-2023  润新知