zoukankan      html  css  js  c++  java
  • (转) 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. }  

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

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

  • 相关阅读:
    流处理 —— Spark Streaming中的Window操作
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.8 提供带注解的限定符元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.7 为自动检测组件提供作用域
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.6 给自动检测组件命名
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.5 在组件中定义bean的元数据
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.4 使用过滤器自定义扫描
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.3 自动检测类和注册bean的定义
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.2 元注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10.1 @Component和深层的构造型注解
    Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.10 类路径扫描和被管理的组件
  • 原文地址:https://www.cnblogs.com/0to9/p/5028301.html
Copyright © 2011-2022 走看看