(转) C#多线程赛跑实例

专于:http://blog.csdn.net/lidatgb/article/details/8363035  

  结合上篇《多线程的基础》,这次我们写一个多线程的赛跑实例,内容很简单:超人和蜘蛛侠赛跑,因为超人飞的比蜘蛛侠跳的快,为了公平,我们让蜘蛛侠跑的长度小点,裁判负责宣布比赛的开始和结束。

[csharp] view plaincopyprint?
 
  1. class MultiThread  
  2.     {  
  3.         //定义两个线程,分别为超人和蜘蛛侠  
  4.         private static Thread SuperMan;  
  5.         private static Thread SpiderMan;  
  6.         //程序入口,比赛开始  
  7.         static void Main(string[] args)  
  8.         {  
  9.             //初始化数据  
  10.             InitData();  
  11.             //裁判吹哨,开始赛跑  
  12.             JudgeWork();  
  13.         }  
  14.   
  15.         /// <summary>  
  16.         /// 初始化超人和蜘蛛侠的线程和姓名  
  17.         /// </summary>  
  18.         private static void InitData()  
  19.         {  
  20.             SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));  
  21.             SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));  
  22.             SuperMan.Name = "SuperMan";  
  23.             SpiderMan.Name = "SpiderMan";  
  24.   
  25.         }  
  26.         /// <summary>  
  27.         /// 裁判开始比赛,最后宣布胜者  
  28.         /// </summary>  
  29.         private static void JudgeWork()  
  30.         {  
  31.             Console.WriteLine("{0}   PK   {1}", SuperMan.Name, SpiderMan.Name);  
  32.             Console.WriteLine("比赛即将开始,请各位做好准备!");  
  33.             Console.WriteLine("预备!");  
  34.             Console.Read();  
  35.             //Superman起跑  
  36.             Console.WriteLine("回车枪响,Superman开始起跑!");  
  37.             Console.Beep(654, 1200);  
  38.             SuperMan.Start(500);  
  39.             //Monster起跑  
  40.             Console.WriteLine("回车枪响,SpiderMan开始起跑!");  
  41.             SpiderMan.Start(200);  
  42.             SuperMan.Join();  
  43.             SpiderMan.Join();  
  44.             //宣布赛跑结果  
  45.             Console.WriteLine("我宣布比赛结束");  
  46.             //程序暂停12秒  
  47.             Thread.Sleep(12000);  
  48.         }  
  49.         /// <summary>  
  50.         /// 赛跑的过程  
  51.         /// </summary>  
  52.         /// <param name="obj">赛跑参数</param>  
  53.         private static void RunnerWork(Object obj)  
  54.         {  
  55.             int length = Int32.Parse(obj.ToString());  
  56.             Thread CurrentThread = Thread.CurrentThread;  
  57.             string CurThreadName = CurrentThread.Name;  
  58.             int speed;  
  59.             //超人速度为20  
  60.             if (CurThreadName == SuperMan.Name)  
  61.             {  
  62.                 speed = 50;  
  63.             }  
  64.             //蜘蛛侠速度为20  
  65.             else if (CurThreadName == SpiderMan.Name)  
  66.             {  
  67.                 speed = 20;  
  68.             }  
  69.             //如果不可控线程进入,采用以下速度  
  70.             else  
  71.             {  
  72.                 speed = 1;  
  73.             }  
  74.             Console.WriteLine("{0},开始起跑…………", CurThreadName);  
  75.             for (int count = speed; count <= length; count += speed)  
  76.             {  
  77.                 Thread.Sleep(1000);  
  78.                 Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());  
  79.             }  
  80.             Console.WriteLine("{0},到达终点!了咧欢迎……", CurThreadName);  
  81.         }  
  82.     }  

        运行结果:

        

            比赛刚刚开始,裁判即宣布结束,这不符合常理。仔细分析可以发现,程序可控制的进程一共有三个,即裁判、超人和蜘蛛侠,三个进程相互独立同时进行,所以裁判宣布比赛开始后即按照它的线程继续宣布结束。
        我们可以这样:在裁判宣布比赛开始后,让蜘蛛侠和超人的线程执行完毕再执行裁判进程:

[csharp] view plaincopyprint?
 
  1. //防止裁判的主进程先结束,让超人和蜘蛛侠的进程先执行完毕  
  2. SuperMan.Join();  
  3. SpiderMan.Join();  
  4. Console.WriteLine("我宣布比赛结束");  

        这次的执行结果为:

        

        赛跑结束,裁判才宣布比赛结束,但是还有问题,裁判总得宣布谁跑赢了吧,台底下这么多粉丝等着呢?这个我们可以用变量的方式保存署名,达到宣布谁为冠军的功能。
        为了展示同步异步读写问题,我们让超人赛跑中去拯救世界,然后回来继续比赛;先到达终点的人,自己花时间找粉笔,然后在黑板上署名,其他人看到黑板上有名字就不能再写,裁判宣布署名的人为胜者。

[csharp] view plaincopyprint?
 
  1. class MultiThread3  
  2. {  
  3.   
  4.     //署名用的黑板  
  5.     static string NameBoard = "";  
  6.     //定义两个线程,分别为超人和蜘蛛侠  
  7.     private static Thread SuperMan;  
  8.     private static Thread SpiderMan;  
  9.     //程序入口,比赛开始  
  10.     static void Main(string[] args)  
  11.     {  
  12.         //初始化数据  
  13.         InitData();  
  14.         //裁判吹哨,开始赛跑  
  15.         JudgeWork();  
  16.     }  
  17.   
  18.     /// <summary>  
  19.     /// 初始化超人和蜘蛛侠的线程和姓名  
  20.     /// </summary>  
  21.     private static void InitData()  
  22.     {  
  23.         SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));  
  24.         SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));  
  25.         SuperMan.Name = "SuperMan";  
  26.         SpiderMan.Name = "SpiderMan";  
  27.   
  28.     }  
  29.     /// <summary>  
  30.     /// 裁判开始比赛,最后宣布胜者  
  31.     /// </summary>  
  32.     private static void JudgeWork()  
  33.     {  
  34.         Console.WriteLine("{0}   PK   {1}", SuperMan.Name, SpiderMan.Name);  
  35.         Console.WriteLine("比赛即将开始,请各位做好准备!");  
  36.         Console.WriteLine("预备!");  
  37.         Console.Read();  
  38.         //Superman起跑  
  39.         Console.WriteLine("回车枪响,SuperMan开始起跑!");  
  40.         Console.Beep(654, 1200);  
  41.         SuperMan.Start(500);  
  42.         //Monster起跑  
  43.         Console.WriteLine("回车枪响,SpiderMan开始起跑!");  
  44.         SpiderMan.Start(300);  
  45.         //防止裁判的主进程先结束,让超人和蜘蛛侠的进程先执行完毕  
  46.         SuperMan.Join();  
  47.         SpiderMan.Join();  
  48.         //宣布赛跑结果  
  49.         AnnounceWinner();  
  50.         //程序暂停12秒  
  51.         Thread.Sleep(12000);  
  52.     }  
  53.     /// <summary>  
  54.     /// 赛跑的过程  
  55.     /// </summary>  
  56.     /// <param name="obj">赛跑参数</param>  
  57.     private static void RunnerWork(Object obj)  
  58.     {  
  59.         int length = Int32.Parse(obj.ToString());  
  60.         Thread CurrentThread = Thread.CurrentThread;  
  61.         string CurThreadName = CurrentThread.Name;  
  62.         int speed;  
  63.         //超人速度为20  
  64.         if (CurThreadName == SuperMan.Name)  
  65.         {  
  66.             speed = 50;  
  67.         }  
  68.         //蜘蛛侠速度为20  
  69.         else if (CurThreadName == SpiderMan.Name)  
  70.         {  
  71.             speed = 20;  
  72.         }  
  73.         //如果不可控线程进入,采用以下速度  
  74.         else  
  75.         {  
  76.             speed = 1;  
  77.         }  
  78.         Console.WriteLine("{0},开始起跑…………", CurThreadName);  
  79.         for (int count = speed; count <= length; count += speed)  
  80.         {  
  81.             Thread.Sleep(1000);  
  82.             Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());  
  83.             //超人跑到一半,去拯救世界  
  84.             if (count == length / 2)  
  85.             {  
  86.                 if (CurThreadName == SuperMan.Name)  
  87.                 {  
  88.                     Console.WriteLine("世界末日来临,超人去拯救世界……");  
  89.                     string waitInfo = "..";  
  90.                     //超人拯救世界过程  
  91.                     for (int j = 0; j <= 10; j++)  
  92.                     {  
  93.                         Console.WriteLine("超人拯救世界中" + waitInfo);  
  94.                         waitInfo += "..";  
  95.                         Thread.Sleep(1000);  
  96.                     }   
  97.                     Console.WriteLine("超人去拯救世界归来,继续赛跑……");  
  98.                 }  
  99.             }  
  100.         }  
  101.         Console.WriteLine("{0},到达终点!乐咧欢迎……", CurThreadName);  
  102.         WriteName(CurThreadName);  
  103.     }  
  104.   
  105.     /// <summary>  
  106.     /// 跑到重点线后,选手自己在黑板上署名  
  107.     /// </summary>   
  108.     /// <param name="name">选手姓名</param>  
  109.     private static void WriteName(string name)  
  110.     {  
  111.         //黑板上没名字,才可以署自己的名字  
  112.         if (NameBoard.Length == 0)  
  113.         {  
  114.             Console.WriteLine("{0}去找粉笔了……", name);  
  115.             //找粉笔花费的时间  
  116.             Thread.Sleep(9000);  
  117.             Console.WriteLine("{0}拿着粉笔回来了,开始署名……", name);  
  118.             NameBoard = name;  
  119.             Console.WriteLine("{0}署完名后,开心的离开了……", name);  
  120.         }  
  121.         //黑板上有署名时不能再署名  
  122.         else  
  123.         {  
  124.             Console.WriteLine("{0}发现已经署名,桑心的离开了……", name);  
  125.         }  
  126.     }  
  127.     /// <summary>  
  128.     /// 宣布比赛结果  
  129.     /// </summary>  
  130.     private static void AnnounceWinner()  
  131.     {  
  132.         Console.WriteLine("我是裁判,我宣布这次比赛的冠军是{0}", NameBoard);  
  133.     }  
  134. }  

        运行结果:

        

        
        可以看到明明是SuperMan还在拯救地球时,SpiderMan已经到达终点,而裁判宣布的冠军却是SuperMan。仔细分析一下程序即可知道:虽然SpiderMan先到达终点,并且先发现黑板是空的,但是在SpiderMan寻找粉笔的过程中,SuperMan到达终点,并且也发现黑板是空的,于是两人都写上了自己的名字,但是因为后者的会覆盖前者的,所以胜利者成了SuperMan,整个过程如下图所示:

        

        问题出现的原因在于,SpiderMan到达以后看到黑板,SuperMan仍然看到黑板,即这个黑板对于两个人都是可写的,后者会覆盖前者的内容,这种方式为异步写。
        怎么克服这个问题?可以使用Lock锁住临界区代码,如下:

[csharp] view plaincopyprint?
 
  1. //定义一个对象类型的objLock  
  2. private static object objLock = new object();  
  3. /// <summary>  
  4. /// 跑到重点线后,选手自己在黑板上署名  
  5. /// </summary>   
  6. /// <param name="name">选手姓名</param>  
  7. private static void WriteName(string name)  
  8. {  
  9.     //采用异步读方式,筛选掉已经看到署名的线程,提高效率  
  10.     //黑板上没名字,才可以署自己的名字  
  11.     if (NameBoard.Length == 0)  
  12.     {  
  13.         //因为上面为异步读,所以可能多个线程可以进入到这一步  
  14.         lock (objLock)  
  15.         {  
  16.             //同步读方式  
  17.             if (NameBoard.Length == 0)  
  18.             {  
  19.                 Console.WriteLine("{0}去找粉笔了……", name);  
  20.                 //找粉笔花费的时间  
  21.                 Thread.Sleep(9000);  
  22.                 Console.WriteLine("{0}拿着粉笔回来了,开始署名……", name);  
  23.                 NameBoard = name;  
  24.                 Console.WriteLine("{0}署完名后,开心地离开了……", name);  
  25.             }  
  26.             //黑板上有署名时不能再署名  
  27.             else  
  28.             {  
  29.                 Console.WriteLine("{0}发现已经署名,桑心地离开了……", name);  
  30.             }  
  31.         }  
  32.     }  
  33. }  

             需要注意的是,锁住的内容(非临界区代码)必须是共享型的引用型数据,因为如果是局部变量针对一个线程锁不锁对其它线程意义不大;采用引用数据类型,可以保证每个线程锁住内容都指向同一个地址。

        为了直观显示,没有抽象出超人、蜘蛛侠和裁判的类,以上就是一个简单的多线程应用实例,当然这是多线程的冰山一角,更多的还有待在以后开发中实践,这次争取在考试系统使用多线程优化抽题和判分等功能。

原文地址:https://www.cnblogs.com/0to9/p/5028301.html