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. }  

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

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

  • 相关阅读:
    iOS-数据存储的常用方式
    Bullet 学习笔记之 Bullet User Manual
    Bullet 学习笔记之 Bullet User Manual
    Bullet 学习笔记之 Bullet User Manual
    Bullet 学习笔记之 CollisionShape 和 CollisionObject
    Bullet Basic Example 示例
    Bullet 学习笔记
    Gazebo 机器人仿真流程之 World 类(二)
    Gazebo 机器人仿真流程之 WorldPrivate 类
    Gazebo 机器人仿真流程之 World 类
  • 原文地址:https://www.cnblogs.com/0to9/p/5028301.html
Copyright © 2011-2022 走看看