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

时间:2023-01-22 12:06:46

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

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

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

运行结果:

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

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

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

这次的执行结果为:

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

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

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

运行结果:

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

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

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

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

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

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

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