• C#多线程线程


     “线程同步”的含义

     

            当一个进程启动了多个线程时,如果需要控制这些线程的推进顺序(比如A线程必须等待B和C线程执行完毕之后才能继续执行),则称这些线程需要进行“线程同步(thread synchronization)”。

            线程同步的道理虽然简单,但却是给多线程开发带来复杂性的根源之一。当线程同步不好时,有可能会出现一种特殊的情形——死锁(Dead Lock)

    “死锁”的含义

     

            死锁表示系统进入了一个僵化状态,所有线程都没有执行完毕,但却谁也没法继续执行。究其根源,是因为“进程推进顺序不当”和“资源共享”。如例:

            1)进程推进顺序不当造成死锁

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. namespace JoinLeadToDeadlock  
    7. {  
    8.     class Program  
    9.     {  
    10.         static Thread mainThread;  
    11.         static void Main(string[] args)  
    12.         {  
    13.             Console.WriteLine("主线程开始运行");  
    14.             mainThread = Thread.CurrentThread;  
    15.   
    16.             Thread ta = new Thread(new ThreadStart(ThreadAMethod));  
    17.             ta.Start();  //线程A开始执行   
    18.             Console.WriteLine("主线程等待线程A结束……");  
    19.             ta.Join();    //等待线程A结束   
    20.             Console.WriteLine("主线程退出");  
    21.         }  
    22.   
    23.         static void ThreadAMethod()  
    24.         {  
    25.             for (int i = 0; i < 10; i++)  
    26.             {  
    27.                 Console.WriteLine(Convert.ToString(i) + ": 线程A正在执行");  
    28.                 Thread.Sleep(1000);  
    29.             }  
    30.             Console.WriteLine("线程A等待主线程退出……");  
    31.             mainThread.Join();  //等待主线程结束   
    32.         }  
    33.     }  
    34. }  
            在该例中,主线程mainThread先开始执行,然后启动线程ta,线程ta执行结束前又要等待mainThread线程执行结束,这样就出现了“交叉等待”的局面,必然死锁!

            2)共享资源造成死锁

            所谓“共享资源”,指的是多个线程可以同时访问的数据结构、文件等信息实体。

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6.   
    7. namespace SharedResourceLeadToDeadlock  
    8. {  
    9.     class Program  
    10.     {  
    11.         //共享资源   
    12.         static SharedResource R1 = new SharedResource();  
    13.         static SharedResource R2 = new SharedResource();  
    14.           
    15.         static void Main(string[] args)  
    16.         {  
    17.             Thread th1 = new Thread(UseSharedResource1);  
    18.             Thread th2 = new Thread(UseSharedResource2);  
    19.             th1.Start();  
    20.             th2.Start();  
    21.             //等待两线程运行结束   
    22.             th1.Join();  
    23.             th2.Join();  
    24.         }  
    25.   
    26.         static void UseSharedResource1()  
    27.         {  
    28.             System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);  
    29.             Monitor.Enter(R1);  //对R1加锁   
    30.             System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);  
    31.             Thread.Sleep(1000);  
    32.             System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);  
    33.             Monitor.Enter(R2);  //对R2加锁   
    34.             System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);  
    35.             Thread.Sleep(1000);  
    36.             System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);  
    37.             Monitor.Exit(R2);   //对R2解锁   
    38.             System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);  
    39.             Monitor.Exit(R1);  //对R1解锁   
    40.         }  
    41.   
    42.         static void UseSharedResource2()  
    43.         {  
    44.             System.Console.WriteLine("线程{0}申请使用资源R2", Thread.CurrentThread.ManagedThreadId);  
    45.             Monitor.Enter(R2);   //对R2加锁   
    46.             System.Console.WriteLine("线程{0}独占使用资源R2", Thread.CurrentThread.ManagedThreadId);  
    47.             Thread.Sleep(500);  
    48.             System.Console.WriteLine("线程{0}申请使用资源R1", Thread.CurrentThread.ManagedThreadId);  
    49.             Monitor.Enter(R1);   //对R1加锁   
    50.             System.Console.WriteLine("线程{0}独占使用资源R1", Thread.CurrentThread.ManagedThreadId);  
    51.             Thread.Sleep(500);  
    52.             System.Console.WriteLine("线程{0}资源R1使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);  
    53.             Monitor.Exit(R1);  //对R1解锁   
    54.             System.Console.WriteLine("线程{0}资源R2使用完毕,放弃", Thread.CurrentThread.ManagedThreadId);  
    55.             Monitor.Exit(R2);   //对R2解锁   
    56.         }  
    57.     }  
    58.   
    59.     class SharedResource  
    60.     {  
    61.     }  
    62. }  
            当多个线程访问同一个数据时,如果不对读和写的顺序作出限定,例如一个线程正在读而另一个数据尝试写,则读数据的线程得到的数据就可能出错。这也是多线程带来的问题。如例:
          
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. namespace SharedResourceLeadToDataError  
    7. {  
    8.     class Program  
    9.     {  
    10.         static void Main(string[] args)  
    11.         {  
    12.             Thread[] ths = new Thread[4];  
    13.             for (int i = 0; i < 4; i++)  
    14.             {  
    15.                 ths[i]=new Thread(increaseCount);  
    16.                 ths[i].Start();  
    17.             }  
    18.             System.Console.ReadKey();  
    19.         }  
    20.   
    21.         static void increaseCount()  
    22.         {  
    23.             Random ran = new Random();  
    24.             Thread.Sleep(ran.Next(100, 5000));  
    25.             int beginNum = SharedResource.Count;  
    26.             System.Console.WriteLine("线程 {0} 读到的起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNum );  
    27.             for (int i = 0; i < 10000; i++)  
    28.             {  
    29.                beginNum ++;  
    30.             }  
    31.             SharedResource.Count = beginNum;  
    32.             System.Console.WriteLine("线程 {0} 结束,SharedResource.Count={1}", Thread.CurrentThread.ManagedThreadId,SharedResource.Count);  
    33.         }  
    34.     }  
    35.   
    36.     class SharedResource  
    37.     {  
    38.         public static int Count = 0;  
    39.     }  
    40. }  
            四个线程同时读写共享变量ShareResource.Count,由于未对读写进行控制,所以必然会造成数据存取错误!
     

    线程同步与并发访问控制手段

     
            正如为了解决车辆交通问题,人们建立了红绿灯的交通控制手段一样,可以为线程设定一套控制机制,以实现线程间的同步,以及保证以正确的顺序来访问共享资源。为了保护应用程序的资源不被破坏,为多线程程序提供了三种加锁的机制,分别是:Monitor类、Lock关键字和Mutex类。
     

    1、Monitor类

     

        (1)使用方法

     
    •  Monitor对象的Enter方法可用于向共享资源申请一把“独占锁”。当一个线程拥有特定共享资源的独占锁时,尝试访问同一共享资源的其他线程只能等待。
    •  Monitor对象的Exit方法用于释放锁。
    •   要注意:Enter与Exit方法必须严格配对,否则,有可能出现死锁情况。
    •  Monitor可以锁定单个对象,也可以锁定一个类型的静态字段或属性
                  1).Monitor.Enter(共享资源对象);
                  2).Monitor.Enter(typeof(共享资源类型));
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. //展示用Monitor访问共享资源   
    7. namespace UseMonitor1  
    8. {  
    9.     class Program  
    10.     {  
    11.         static void Main(string[] args)  
    12.         {  
    13.             SharedResource obj = new SharedResource();  
    14.   
    15.             Thread[] ths = new Thread[4];  
    16.             for (int i = 0; i < 4; i++)  
    17.             {  
    18.                 ths[i] = new Thread(increaseCount);  
    19.                 ths[i].Start(obj);  
    20.             }  
    21.             System.Console.ReadKey();  
    22.         }  
    23.         static void increaseCount(Object obj)  
    24.         {  
    25.            //访问实例字段   
    26.             VisitDynamicField(obj);  
    27.             //访问静态字段   
    28.             VisitStaticField();  
    29.         }  
    30.   
    31.         //访问静态字段   
    32.         private static void VisitStaticField()  
    33.         {  
    34.             //访问静态字段   
    35.             Monitor.Enter(typeof(SharedResource));  
    36.   
    37.             int beginNumber = SharedResource.StaticCount;  
    38.             System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);  
    39.             for (int i = 0; i < 10000; i++)  
    40.             {  
    41.                 beginNumber++;  
    42.             }  
    43.             SharedResource.StaticCount = beginNumber;  
    44.             System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",  
    45.             Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);  
    46.   
    47.             Monitor.Exit(typeof(SharedResource));  
    48.         }  
    49.   
    50.         //访问实例字段   
    51.         private static void VisitDynamicField(Object obj)  
    52.         {  
    53.             Monitor.Enter(obj);  
    54.   
    55.             int beginNumber = (obj as SharedResource).DynamicCount;  
    56.             System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);  
    57.             for (int i = 0; i < 10000; i++)  
    58.             {  
    59.                 beginNumber++;  
    60.             }  
    61.             (obj as SharedResource).DynamicCount = beginNumber;  
    62.             System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",  
    63.             Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);  
    64.   
    65.             Monitor.Exit(obj);  
    66.         }  
    67.     }  
    68.     //共享资源类   
    69.     class SharedResource  
    70.     {  
    71.         public int DynamicCount = 0;        //多线程共享的实例字段   
    72.         public static int StaticCount = 0;  //多线程共享的静态字段   
    73.     }  
    74. }  
              Monitor类的使用模板:

            Monitor.Enter(共享资源对象); //申请对象锁

            //得到了对象锁,可以对共享资源进行访问,
            //其他线程只能等待
            //访问共享资源
            //对共享资源的访问完成,释放对象锁,
            //让其他线程有机会访问共享资源

            Monitor.Exit(obj);

         (2)Monitor的特殊注意之处:

     
            Monitor一般只用于访问引用类型的共享资源,如果将其施加于值类型变量,则值类型变量将会被装箱,而当调用Exit方法时,虽然是同一个值类型变量,但实际上此值类型变量又会被第二次装箱,这将导致Enter方法所访问的对象与Exit方法所访问的不是同一个,Monitor对象将会引发SynchronizationLockException。
            因此,不要将Monitor用于值类型!
     

         (3) Monitor.Wait()和Monitor.Pulse()

     
            Wait()释放对象上的锁,以便允许其他线程锁定和访问该对象。在其他线程访问对象时,调用线程将等待。
            Pulse(),PulseAll()向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改,并且锁的所有者准备释放该锁。等待线程被放置在对象的就绪队列中以便它可以最后接收对象锁。一旦线程拥有了锁,它就可以检查对象的新状态以查看是否达到所需状态。PulseAll与Pulse方法类似,不过它是向所有在阻塞队列中的进程发送通知信号,如果只有一个线程被阻塞,那么请使用Pulse方法。
            注意:Pulse、PulseAll和Wait方法必须从同步的代码块内调用。
            例1:
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. namespace UseMonitor2  
    7. {  
    8.     class Program  
    9.     {  
    10.         static void Main(string[] args)  
    11.         {  
    12.             //创建共享资源   
    13.             SharedResource obj = new SharedResource();  
    14.             //创建线程对象并启动   
    15.             Thread tha = new Thread(ThreadMethodA);  
    16.             Thread thb = new Thread(ThreadMethodB);  
    17.             tha.Start(obj);  
    18.             thb.Start(obj);  
    19.   
    20.             //程序暂停   
    21.             System.Console.ReadKey();  
    22.         }  
    23.   
    24.         static void ThreadMethodA(Object obj)  
    25.         {  
    26.             Monitor.Enter(obj);  
    27.             (obj as SharedResource).DynamicCount += 100;  
    28.             System.Console.WriteLine("线程A完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);  
    29.             Monitor.Pulse(obj); //通知B线程进入准备队列   
    30.             Monitor.Exit(obj);  
    31.         }  
    32.   
    33.         static void ThreadMethodB(Object obj)  
    34.         {  
    35.              Monitor.Enter(obj);  
    36.             //A线程还未工作,因为字段保持初始值0   
    37.             //如果注释掉此条件判断语句,则有可能会发生死锁   
    38.             if((obj as SharedResource).DynamicCount == 0)  
    39.                 Monitor.Wait(obj);//将本线程阻塞,进入阻塞队列等待   
    40.             (obj as SharedResource).DynamicCount += 100;  
    41.             System.Console.WriteLine("线程B完成工作,obj.DynamicCount={0}", (obj as SharedResource).DynamicCount);  
    42.             Monitor.Exit(obj);  
    43.         }  
    44.     }  
    45.   
    46.     //共享资源类   
    47.     class SharedResource  
    48.     {  
    49.         public int DynamicCount = 0;        //多线程共享的实例字段   
    50.     }  
    51. }  
     
            例2:
    1. using System.Threading;  
    2. public class Program   
    3. {  
    4.    static object ball = new object();  
    5.    public static void Main()   
    6.    {  
    7.       Thread threadPing = new Thread( ThreadPingProc );  
    8.       Thread threadPong = new Thread( ThreadPongProc );  
    9.       threadPing.Start(); threadPong.Start();  
    10.    }  
    11.    static void ThreadPongProc()   
    12.    {  
    13.       System.Console.WriteLine("ThreadPong: Hello!");  
    14.       lock ( ball )  
    15.          for (int i = 0; i < 5; i++)  
    16.          {  
    17.             System.Console.WriteLine("ThreadPong: Pong ");  
    18.             Monitor.Pulse( ball );  
    19.             Monitor.Wait( ball );  
    20.          }  
    21.       System.Console.WriteLine("ThreadPong: Bye!");  
    22.    }  
    23.    static void ThreadPingProc()   
    24.    {  
    25.       System.Console.WriteLine("ThreadPing: Hello!");  
    26.       lock ( ball )  
    27.          for(int i=0; i< 5; i++)  
    28.          {  
    29.             System.Console.WriteLine("ThreadPing: Ping ");  
    30.             Monitor.Pulse( ball );  
    31.             Monitor.Wait( ball );  
    32.          }  
    33.       System.Console.WriteLine("ThreadPing: Bye!");  
    34.    }  
    35. }  
    可能的执行结果:
    1. ThreadPing: Hello!  
    2. ThreadPing: Ping  
    3. ThreadPong: Hello!  
    4. ThreadPong: Pong  
    5. ThreadPing: Ping  
    6. ThreadPong: Pong  
    7. ThreadPing: Ping  
    8. ThreadPong: Pong  
    9. ThreadPing: Ping  
    10. ThreadPong: Pong  
    11. ThreadPing: Ping  
    12. ThreadPong: Pong  
    13. ThreadPing: Bye!  
            当threadPing进程进入ThreadPingProc锁定ball并调用Monitor.Pulse( ball )后,它通知threadPong从阻塞队列进入准备队列,当threadPing调用Monitor.Wait( ball )阻塞自己后,它放弃了了对ball的锁定,所以threadPong得以执行。        
               因此,可以借助Monitor.Pulse()来控制进程的推进顺序。
     
    1. //A线程执行的代码  
    2. lock(obj)  
    3. {  
    4.    //访问共享资源obj  
    5.    Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了  
    6. }  
    7. ---------------------------------------------------------------  
    8. //B线程执行的代码  
    9. lock(obj)  
    10. {  
    11.    Monitor.Wait(obj); //等待A 线程完成  
    12.    //访问共享资源obj  
    13. }  
    //A线程执行的代码
    lock(obj)
    {
       //访问共享资源obj
       Monitor.Pulse(obj); //通知B 线程可以访问共享资源obj了
    }
    ---------------------------------------------------------------
    //B线程执行的代码
    lock(obj)
    {
       Monitor.Wait(obj); //等待A 线程完成
       //访问共享资源obj
    }

    2、Lock关键字

            C#使用Lock关键字来简化Monitor的用法。lock就是对Monitor的Enter和Exit的一个封装,而且使用起来更简洁,因此Monitor类的Enter()和Exit()方法的组合使用可以用lock关键字替代。
     
    lock (obj)
    {
           //访问共享资源代码段
    }
    等价于:
    Monitor.Enter(obj);
            //访问共享资源代码段
    Monitor.Exit(obj);
     
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. //展示用Monitor访问共享资源   
    7. namespace UseMonitor1  
    8. {  
    9.     class Program  
    10.     {  
    11.         static void Main(string[] args)  
    12.         {  
    13.             SharedResource obj = new SharedResource();  
    14.   
    15.             Thread[] ths = new Thread[4];  
    16.             for (int i = 0; i < 4; i++)  
    17.             {  
    18.                 ths[i] = new Thread(increaseCount);  
    19.                 ths[i].Start(obj);  
    20.             }  
    21.             System.Console.ReadKey();  
    22.         }  
    23.         static void increaseCount(Object obj)  
    24.         {  
    25.            //访问实例字段   
    26.             VisitDynamicField(obj);  
    27.             //访问静态字段   
    28.             VisitStaticField();  
    29.         }  
    30.   
    31.         //访问静态字段   
    32.         private static void VisitStaticField()  
    33.         {  
    34.             //访问静态字段   
    35.             lock (typeof(SharedResource))  
    36.             {  
    37.                 int beginNumber = SharedResource.StaticCount;  
    38.                 System.Console.WriteLine("线程 {0} 读到的StaticCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);  
    39.                 for (int i = 0; i < 10000; i++)  
    40.                 {  
    41.                     beginNumber++;  
    42.                 }  
    43.                 SharedResource.StaticCount = beginNumber;  
    44.                 System.Console.WriteLine("线程 {0} 结束, SharedResource.StaticCount={1}",  
    45.                 Thread.CurrentThread.ManagedThreadId, SharedResource.StaticCount);  
    46.             }  
    47.         }  
    48.   
    49.         //访问实例字段   
    50.         private static void VisitDynamicField(Object obj)  
    51.         {  
    52.             lock (obj)  
    53.             {  
    54.                 int beginNumber = (obj as SharedResource).DynamicCount;  
    55.                 System.Console.WriteLine("线程 {0} 读到的DynamicCount起始值为 {1}  ", Thread.CurrentThread.ManagedThreadId, beginNumber);  
    56.                 for (int i = 0; i < 10000; i++)  
    57.                 {  
    58.                     beginNumber++;  
    59.                 }  
    60.                 (obj as SharedResource).DynamicCount = beginNumber;  
    61.                 System.Console.WriteLine("线程 {0} 结束,Obj.DynamicCount={1}",  
    62.                 Thread.CurrentThread.ManagedThreadId, (obj as SharedResource).DynamicCount);  
    63.             }  
    64.         }  
    65.     }  
    66.     //共享资源类   
    67.     class SharedResource  
    68.     {  
    69.         public int DynamicCount = 0;        //多线程共享的实例字段   
    70.         public static int StaticCount = 0;  //多线程共享的静态字段   
    71.     }  
    72. }  
                }
         3、自旋锁SpinLock
     
            当一个线程需要访问共享资源时,它可以调用SpinLock.Enter或SpinLock.TryEnter方法申请独占锁,如果暂时不能获得锁(这时可能运行于另一个CPU核上的线程正在访问共享资源),当前线程就会“空转”若干个时钟周期,然后再次尝试。在这个过程中,线程的状态仍是Running,从而避免了操作系统进行一次线程上下文切换所带来的开销。
     
    1. public class MyType  
    2. {  
    3.     //创建自旋锁对象  
    4.     private SpinLock _spinLock = new SpinLock();  
    5.     //将被多线程执行的代码,  
    6.     //由于使用了自旋锁,可以保证被“锁定”代码一次只会被一个线程执行  
    7.     public void DoWork()  
    8.     {  
    9.         bool lockTaken = false;   
    10.         try  
    11.         {  
    12.             _spinLock.Enter(ref lockTaken); //申请获取“锁”  
    13.             // 获得了锁,在此书写工作代码,这些工作代码不会同时被两个线程执行  
    14.         }  
    15.         finally   
    16.         {   
    17.         //工作完毕,或者发生异常时,检查一下当前线程是否占有了锁  
    18.         //如果占有了锁,释放它,以避免出现死锁的情况。  
    19.             if (lockTaken)  _spinLock.Exit();  
    20.         }  
    21.     }  
    22. }  
      

    4、实现原子操作——Interlocked类

     
            Interlocked类是一种互锁操作,提供对多个线程共享的变量进行同步访问的方法,互锁操作具有原子性,即整个操作时不能由相同变量上的另一个互锁操作所中断的单元。
            这个类提供了Increment、Decrement、Add静态方法用于对int或long型变量的递增、递减或相加操作。还提供了Exchange(为整型或引用对象赋值)、CompareExchange(比较后再对整型或引用对象赋值),用于为整型或引用类型的赋值提供原子操作。
            在大多数计算机上,增加变量操作不是一个原子操作,需要执行下列步骤:
    1. 将实例变量中的值加载到寄存器中。
    2. 增加或减少该值。
    3. 在实例变量中存储该值。
            如果不使用 Increment 和 Decrement,线程会在执行完前两个步骤后被抢先。 然后由另一个线程执行所有三个步骤。 当第一个线程重新开始执行时,它覆盖实例变量中的值,造成第二个线程执行增减操作的结果丢失。
     
    利用Interlocked类类解决生产者-消费者关系中的竞争条件问题:(例子来自《周长发——c#面向对象编程》
    1. // Interlocked.cs   
    2. // Interlocked示例   
    3.   
    4. using System;  
    5. using System.Threading;  
    6.   
    7. class Test  
    8. {  
    9.     private long bufferEmpty = 0;  
    10.     private string buffer = null;  
    11.   
    12.     static void Main()  
    13.     {  
    14.         Test t = new Test();  
    15.         // 进行测试   
    16.         t.Go();  
    17.     }  
    18.   
    19.     public void Go()  
    20.     {  
    21.         Thread t1 = new Thread(new ThreadStart(Producer));  
    22.         t1.Name = "生产者线程";  
    23.         t1.Start();  
    24.   
    25.         Thread t2 = new Thread(new ThreadStart(Consumer));  
    26.         t2.Name = "消费者线程";  
    27.         t2.Start();  
    28.   
    29.         // 等待两个线程结束   
    30.         t1.Join();  
    31.         t2.Join();  
    32.     }  
    33.   
    34.     // 生产者方法   
    35.     public void Producer()  
    36.     {  
    37.         Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);  
    38.   
    39.         try  
    40.         {  
    41.             for (int j = 0; j < 16; ++j)  
    42.             {  
    43.                 // 等待共享缓冲区为空   
    44.                 while (Interlocked.Read(ref bufferEmpty) != 0)   
    45.                     Thread.Sleep(100);  
    46.   
    47.                 // 构造共享缓冲区   
    48.                 Random r = new Random();  
    49.                 int bufSize = r.Next() % 64;  
    50.                 char[] s = new char[bufSize];  
    51.                 for (int i = 0; i < bufSize; ++i)  
    52.                 {  
    53.                     s[i] = (char)((int)'A' + r.Next() % 26);  
    54.                 }  
    55.                 buffer = new string(s);  
    56.   
    57.                 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);  
    58.   
    59.                 // 互锁加一,成为1,标志共享缓冲区已满   
    60.                 Interlocked.Increment(ref bufferEmpty);  
    61.   
    62.                 // 休眠,将时间片让给消费者   
    63.                 Thread.Sleep(10);  
    64.             }  
    65.   
    66.             Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);  
    67.         }  
    68.         catch (System.Threading.ThreadInterruptedException)  
    69.         {  
    70.             Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);  
    71.         }  
    72.     }  
    73.   
    74.     // 消费者方法   
    75.     public void Consumer()  
    76.     {  
    77.         Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);  
    78.   
    79.         try  
    80.         {  
    81.             for (int j = 0; j < 16; ++j)  
    82.             {  
    83.                 while (Interlocked.Read(ref bufferEmpty) == 0)  
    84.                     Thread.Sleep(100);  
    85.   
    86.                 // 打印共享缓冲区   
    87.                 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, buffer);  
    88.   
    89.                 // 互锁减一,成为0,标志共享缓冲区已空   
    90.                 Interlocked.Decrement(ref bufferEmpty);  
    91.   
    92.                 // 休眠,将时间片让给生产者   
    93.                 Thread.Sleep(10);  
    94.             }  
    95.   
    96.             Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);  
    97.         }  
    98.         catch (System.Threading.ThreadInterruptedException)  
    99.         {  
    100.             Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);  
    101.         }  
    102.     }  
    103. }  
     5、Mutex类
     
             Mutex与Monitor类似,需要注意的是Mutex分两种:一种是本地Mutex一种是系统级Mutex,系统级Mutex可以用来进行跨进程间的线程的同步。尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
            一个线程要想访问共享资源,它必须调用Mutex对象的Wait系列方法之一提出申请。当申请得到批准的线程完成了对于共享资源的访问后,它调用Mutex对象的ReleaseMutex()方法释放对于共享资源的访问权。
     
           利用多线程模拟3个人在ATM上多次提款操作:
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. namespace UseATM  
    7. {  
    8.     class Program  
    9.     {  
    10.         static ATM OneATM=new ATM(); //共享资源   
    11.         static void Main(string[] args)  
    12.         {  
    13.             //向公共帐号存款2万   
    14.   
    15.             Console.Write("输入公司公共帐户的金额:");  
    16.             int PublicAcountMoney =Convert.ToInt32(Console.ReadLine());  
    17.             OneATM.Deposit(PublicAcountMoney);  
    18.   
    19.             Console.Write("输入ATM中的现金额:");  
    20.             int ATMLeftMoney = Convert.ToInt32(Console.ReadLine());  
    21.             OneATM.SetATMLeftMoney(ATMLeftMoney);  
    22.   
    23.             System.Console.WriteLine(" 敲任意键从公共帐户中取钱,ESC键退出…… ");  
    24.   
    25.             while (System.Console.ReadKey(true).Key !=ConsoleKey.Escape)  
    26.             {  
    27.                 System.Console.WriteLine("");  
    28.                 Thread One = new Thread(WithDrawMoney);  
    29.                 Thread Two = new Thread(WithDrawMoney);  
    30.                 Thread Three = new Thread(WithDrawMoney);  
    31.   
    32.                 //随机生成一个要提款的数额,最少100元,最高5000元   
    33.                 Random ran = new Random();  
    34.                 One.Start(ran.Next(100, 5000));  
    35.                 Two.Start(ran.Next(100, 5000));  
    36.                 Three.Start(ran.Next(100, 5000));  
    37.   
    38.                 //等三人取完钱   
    39.                 One.Join();  
    40.                 Two.Join();  
    41.                 Three.Join();  
    42.   
    43.                 System.Console.WriteLine("公共账号剩余{0}元,ATM中可提现金:{1}", OneATM.QueryPublicAccount(),OneATM.QueryATMLeftAccount());  
    44.             }  
    45.         }  
    46.   
    47.         //线程函数   
    48.         static void WithDrawMoney(object amount)  
    49.         {  
    50.             switch(OneATM.WithDraw((int)amount))  
    51.             {  
    52.                 case WithDrawState.Succeed:  
    53.                     System.Console.WriteLine("成功取出{0}元。",amount );  
    54.                     break;  
    55.                 case WithDrawState.ATMHasNotEnoughCash:  
    56.                     System.Console.WriteLine("ATM中现金不足,无法支取{0}元。", amount);  
    57.                     break ;  
    58.                 case WithDrawState.AccountHasNotEnoughMoney:  
    59.                     System.Console.WriteLine("帐户中没钱了!无法取出{0}元",amount);  
    60.                     break ;  
    61.             }                
    62.         }  
    63.     }  
    64.   
    65.     //自助取款机   
    66.     class ATM  
    67.     {  
    68.         private int PublicAcountLeftMoney;//帐户剩余的钱   
    69.         private int ATMLeftMoney;//提款机剩余的钱   
    70.   
    71.         //同步信息号量   
    72.         private Mutex m = new Mutex();  
    73.   
    74.         //取钱   
    75.         public WithDrawState WithDraw(int amount)  
    76.         {  
    77.             m.WaitOne();  
    78.             //公共帐号钱不够   
    79.             if (PublicAcountLeftMoney < amount)  
    80.             {  
    81.                 m.ReleaseMutex();  
    82.                 return WithDrawState.AccountHasNotEnoughMoney;  
    83.             }  
    84.             //ATM现金不够   
    85.             if (ATMLeftMoney < amount)  
    86.             {  
    87.                 m.ReleaseMutex();  
    88.                 return WithDrawState.ATMHasNotEnoughCash;  
    89.             }  
    90.             //用户可以提取现金   
    91.             ATMLeftMoney -= amount;  
    92.             PublicAcountLeftMoney -= amount;  
    93.             m.ReleaseMutex();  
    94.             return WithDrawState.Succeed;  
    95.         }  
    96.         //存钱   
    97.         public void Deposit(int amount)  
    98.         {  
    99.             m.WaitOne();  
    100.             PublicAcountLeftMoney += amount;  
    101.             m.ReleaseMutex();  
    102.         }  
    103.   
    104.         /// <summary>   
    105.         /// 设置ATM的现金金额   
    106.         /// </summary>   
    107.         /// <param name="amount"></param>   
    108.         public void SetATMLeftMoney(int amount)  
    109.         {  
    110.             Interlocked.Exchange(ref ATMLeftMoney, amount);  
    111.         }  
    112.         //获取还剩余多少钱   
    113.         public int QueryPublicAccount()  
    114.         {  
    115.             return PublicAcountLeftMoney;  
    116.         }  
    117.   
    118.         /// <summary>   
    119.         /// 查询ATM剩余多少钱   
    120.         /// </summary>   
    121.         /// <returns></returns>   
    122.         public int QueryATMLeftAccount()  
    123.         {  
    124.             return ATMLeftMoney;  
    125.         }  
    126.     }  
    127.     //取款状态   
    128.     public enum WithDrawState  
    129.     {  
    130.         Succeed,        //取钱成功   
    131.         AccountHasNotEnoughMoney, //账号中没钱了   
    132.         ATMHasNotEnoughCash  //ATM中没有足够的现金   
    133.     }  
    134. }  
    可能的运行结果:
    1. 输入公司公共帐户的金额:200000  
    2. 输入ATM中的现金额:6000000  
    3.   
    4. 敲任意键从公共帐户中取钱,ESC键退出……  
    5.   
    6.   
    7. 成功取出1249元。  
    8. 成功取出643元。  
    9. 成功取出4958元。  
    10. 公共账号剩余193150元,ATM中可提现金:5993150  
    11.   
    12. 成功取出1168元。  
    13. 成功取出3650元。  
    14. 成功取出2707元。  
    15. 公共账号剩余185625元,ATM中可提现金:5985625  
    16.   
    17. 成功取出3866元。  
    18. 成功取出402元。  
    19. 成功取出2397元。  
    20. 公共账号剩余178960元,ATM中可提现金:5978960  
    21.   
    22. 成功取出4485元。  
    23. 成功取出1701元。  
    24. 成功取出3354元。  
    25. 公共账号剩余169420元,ATM中可提现金:5969420  
            Mustex与Monitor有一个很大的区别:
            Mutex可以用来同步属于不同应用程序或者进程的线程,而Monitor没有这个能力。

            为了说明这个区别,我们将生产者和消费者线程分别放在两个应用程序中,在两个应用程序中都各自创建一个同名的Mutex对象,并利用他们来对生产者和消费者线程同步:
    生产者线程所在应用程序代码:
    1. // Mutex1.cs   
    2. // Mutex1示例   
    3.   
    4. using System;  
    5. using System.IO;  
    6. using System.Threading;  
    7. using System.Diagnostics;  
    8.   
    9. class Test  
    10. {  
    11.     static void Main()  
    12.     {  
    13.         Test t = new Test();  
    14.         // 进行测试   
    15.         t.Go();  
    16.     }  
    17.   
    18.     public void Go()  
    19.     {  
    20.         // 创建并启动线程   
    21.         Thread t1 = new Thread(new ThreadStart(Producer));  
    22.         t1.Name = "生产者线程";  
    23.         t1.Start();  
    24.   
    25.         // 等待线程结束   
    26.         t1.Join();  
    27.   
    28.         Console.WriteLine("按Enter键退出...");  
    29.         Console.Read();  
    30.     }  
    31.   
    32.     // 生产者方法   
    33.     public void Producer()  
    34.     {  
    35.         Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);  
    36.   
    37.         // 创建互斥体   
    38.         Mutex mutex = new Mutex(false"CSharp_Mutex_test");  
    39.   
    40.         // 启动消费者进程   
    41.         Process.Start("Mutex2.exe");  
    42.   
    43.         for (int j = 0; j < 16; ++j)  
    44.         {  
    45.             try  
    46.             {  
    47.                 // 进入互斥体   
    48.                 mutex.WaitOne();  
    49.   
    50.                 FileStream fs = new FileStream(@"d: ext.txt", FileMode.OpenOrCreate, FileAccess.Write);  
    51.                 StreamWriter sw = new StreamWriter(fs);  
    52.                 // 构造字符串   
    53.                 Random r = new Random();  
    54.                 int bufSize = r.Next() % 64;  
    55.                 char[] s = new char[bufSize];  
    56.                 for (int i = 0; i < bufSize; ++i)  
    57.                 {  
    58.                     s[i] = (char)((int)'A' + r.Next() % 26);  
    59.                 }  
    60.                 string str = new string(s);  
    61.                 // 将字符串写入文件   
    62.                 sw.WriteLine(str);  
    63.                 sw.Close();  
    64.   
    65.                 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, str);  
    66.             }  
    67.             catch (System.Threading.ThreadInterruptedException)  
    68.             {  
    69.                 Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);  
    70.                 break;  
    71.             }  
    72.             finally  
    73.             {  
    74.                 // 退出互斥体   
    75.                 mutex.ReleaseMutex();  
    76.             }  
    77.   
    78.             // 休眠,将时间片让给消费者   
    79.             Thread.Sleep(1000);  
    80.         }  
    81.   
    82.         // 关闭互斥体   
    83.         mutex.Close();  
    84.         Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);  
    85.     }  
    86. }  
     消费者线程所在应用程序代码:
    1. // Mutex2.cs   
    2. // Mutex2示例   
    3.   
    4. using System;  
    5. using System.IO;  
    6. using System.Threading;  
    7.   
    8. class Test  
    9. {  
    10.     static void Main()  
    11.     {  
    12.         Test t = new Test();  
    13.         // 进行测试   
    14.         t.Go();  
    15.     }  
    16.   
    17.     public void Go()  
    18.     {  
    19.         // 创建并启动线程   
    20.         Thread t2 = new Thread(new ThreadStart(Consumer));  
    21.         t2.Name = "消费者线程";  
    22.         t2.Start();  
    23.   
    24.         // 等待线程结束   
    25.         t2.Join();  
    26.   
    27.         Console.WriteLine("按Enter键退出...");  
    28.         Console.Read();  
    29.     }  
    30.   
    31.     // 消费者方法   
    32.     public void Consumer()  
    33.     {  
    34.         Console.WriteLine("{0}:开始执行", Thread.CurrentThread.Name);  
    35.   
    36.         // 创建互斥体   
    37.         Mutex mutex = new Mutex(false"CSharp_Mutex_test");  
    38.   
    39.         for (int j = 0; j < 16; ++j)  
    40.         {  
    41.             try  
    42.             {  
    43.                 // 进入互斥体   
    44.                 mutex.WaitOne();  
    45.   
    46.                 StreamReader sr = new StreamReader(@"d: ext.txt");  
    47.                 string s = sr.ReadLine();  
    48.                 sr.Close();  
    49.   
    50.                 // 显示字符串的值   
    51.                 Console.WriteLine("{0}:{1}", Thread.CurrentThread.Name, s);  
    52.             }  
    53.             catch (System.Threading.ThreadInterruptedException)  
    54.             {  
    55.                 Console.WriteLine("{0}:被终止", Thread.CurrentThread.Name);  
    56.                 break;  
    57.             }  
    58.             finally  
    59.             {  
    60.                 // 退出互斥体   
    61.                 mutex.ReleaseMutex();  
    62.             }  
    63.   
    64.             // 休眠,将时间片让给消费者   
    65.             Thread.Sleep(1000);  
    66.         }  
    67.   
    68.         // 关闭互斥体   
    69.         mutex.Close();  
    70.         Console.WriteLine("{0}:执行完毕", Thread.CurrentThread.Name);  
    71.     }  
    72. }  
    我们分别编译这两个文件,然后运行Mutex1,他会在另一个窗口中启动Mutex2,可能的结果如下:
     
     

    6、Semaphore

     
            Semaphore可以限制可同时访问某一资源或资源池的线程数。
            Semaphore类在内部维护一个计数器,当一个线程调用Semaphore对象的Wait系列方法时,此计数器减一,只要计数器还是一个正数,线程就不会阻塞。当计数器减到0时,再调用Semaphore对象Wait系列方法的线程将被阻塞,直到有线程调用Semaphore对象的Release()方法增加计数器值时,才有可能解除阻塞状态。
     
    示例说明:
    图书馆都配备有若干台公用计算机供读者查询信息,当某日读者比较多时,必须排队等候。UseLibraryComputer实例用多线程模拟了多人使用多台计算机的过程
     
    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Text;  
    4. using System.Threading;  
    5.   
    6. namespace UseLibraryComputer  
    7. {  
    8.     class Program  
    9.     {  
    10.         //图书馆拥有的公用计算机   
    11.         private const int ComputerNum = 3;  
    12.         private static Computer[] LibraryComputers;  
    13.         //同步信号量   
    14.         public static  Semaphore sp = new Semaphore( ComputerNum, ComputerNum);  
    15.   
    16.         static void Main(string[] args)  
    17.         {  
    18.             //图书馆拥有ComputerNum台电脑   
    19.             LibraryComputers = new Computer[ComputerNum];  
    20.             for (int i = 0; i <ComputerNum; i++)  
    21.                 LibraryComputers[i] = new Computer("Computer"+(i+1).ToString());  
    22.             int peopleNum = 0;  
    23.             Random ran=new Random();  
    24.             Thread user;  
    25.             System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……" ,ComputerNum);  
    26.             //每次创建若干个线程,模拟人排队使用计算机   
    27.             while (System.Console.ReadKey().Key  != ConsoleKey.Escape)  
    28.             {  
    29.                 peopleNum = ran.Next(0, 10);  
    30.                 System.Console.WriteLine(" 有{0}人在等待使用计算机。",peopleNum );  
    31.   
    32.                 for (int i = 1; i <= peopleNum; i++)  
    33.                 {  
    34.                     user = new Thread(UseComputer);  
    35.                     user.Start("User" + i.ToString());  
    36.                 }  
    37.             }  
    38.         }  
    39.   
    40.         //线程函数   
    41.         static void UseComputer(Object UserName)  
    42.         {  
    43.             sp.WaitOne();//等待计算机可用   
    44.               
    45.             //查找可用的计算机   
    46.             Computer cp=null;  
    47.             for (int i = 0; i < ComputerNum; i++)  
    48.                 if (LibraryComputers[i].IsOccupied == false)  
    49.                 {  
    50.                     cp = LibraryComputers[i];  
    51.                     break;  
    52.                 }  
    53.             //使用计算机工作   
    54.             cp.Use(UserName.ToString());  
    55.   
    56.             //不再使用计算机,让出来给其他人使用   
    57.             sp.Release();  
    58.         }  
    59.     }  
    60.   
    61.     class Computer  
    62.     {  
    63.         public readonly string ComputerName = "";  
    64.         public Computer(string Name)  
    65.         {  
    66.             ComputerName = Name;  
    67.         }  
    68.         //是否被占用   
    69.         public  bool IsOccupied = false;  
    70.         //人在使用计算机   
    71.         public  void Use(String userName)  
    72.         {  
    73.             System.Console.WriteLine("{0}开始使用计算机{1}", userName,ComputerName);  
    74.             IsOccupied = true;  
    75.             Thread.Sleep(new Random().Next(1, 2000)); //随机休眠,以模拟人使用计算机   
    76.             System.Console.WriteLine("{0}结束使用计算机{1}", userName,ComputerName);  
    77.             IsOccupied = false;  
    78.         }  
    79.     }  
    80. }  
      可能的运行结果:
    (2012/5/31 22:51 It's time to go to sleep......)睡觉
  • 相关阅读:
    复杂json后端解析出现第二层无数据的问题
    idea启动springboot项目报Error running 'ServiceStarter': Command line is too long. Shorten command line for ServiceStarter or also for Application
    docker强制关闭命令
    scala下实现actor多线程基础
    orcale数据库分配用户
    多线程实现互相通信
    从一份配置清单详解 Nginx 服务器配置
    PostgreSQL CentOS 7 安装配置
    .net core session部分浏览器或移动客户端不可用
    VS2019 远程调试
  • 原文地址:https://www.cnblogs.com/qiyecao/p/3303588.html
Copyright © 2020-2023  润新知