前面讲到了,直接使用Thread来运行代码,每次都会创建一个新的线程,如果频繁运行会带来不小的性能损耗,可以通过线程池来解决这个问题。

Thread每次都是新线程示例

示例代码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. namespace ThreadDemo1Start
  5. {
  6. class Program
  7. {
  8. const int Count = 100;
  9. static void Main(string[] args)
  10. {
  11. List<Thread> threads = new List<Thread>();
  12. for (var i = 0; i < 10; i++)
  13. {
  14. //指定要在新线程中运行的代码,将在控制台中输出数字
  15. ParameterizedThreadStart threadStart = DoWork;
  16. //创建一个新的线程实例
  17. Thread thread = new Thread(threadStart);
  18. //开始运行新的线程实例
  19. thread.Start(i.ToString());
  20. threads.Add(thread);
  21. }
  22. //此处的代码不会被上面的start阻塞,是会同时运行的
  23. //在控制台中输出-号
  24. DoWork("-");
  25. //等待线程执行完成
  26. foreach(Thread thread in threads)
  27. {
  28. thread.Join();
  29. }
  30. //全部执行完毕
  31. Console.WriteLine("全部执行完毕");
  32. }
  33. static void DoWork(object? obj)
  34. {
  35. //这里输出一下运行当前代码的线程的id,以便区分是否是新线程还是相同的线程
  36. string str = $"{Thread.CurrentThread.ManagedThreadId}*{obj} ";
  37. for(var i = 0; i < Count; i++)
  38. {
  39. Console.Write(str);
  40. }
  41. }
  42. }
  43. }

示例结果

image.png
从上面的运行结果可以看出,61,72,83,94,105,116,127,138,14*9。所有线程的id都是不相同的。

线程池

示例代码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. namespace ThreadDemo1Start
  5. {
  6. class Program
  7. {
  8. const int Count = 100;
  9. static void Main(string[] args)
  10. {
  11. for (var i = 0; i < 10; i++)
  12. {
  13. //使用线程池来运行所有的
  14. ThreadPool.QueueUserWorkItem(DoWork, i.ToString());
  15. }
  16. //此处的代码不会被上面的start阻塞,是会同时运行的
  17. //在控制台中输出-号
  18. DoWork("-");
  19. //使用线程池是不知道线程池的任务什么时候会执行完成的,此处先睡眠一段时间,等待执行完成
  20. Thread.Sleep(TimeSpan.FromSeconds(3));
  21. //全部执行完毕
  22. Console.WriteLine("全部执行完毕");
  23. }
  24. static void DoWork(object? obj)
  25. {
  26. string str = $"{Thread.CurrentThread.ManagedThreadId}*{obj} ";
  27. for(var i = 0; i < Count; i++)
  28. {
  29. Console.Write(str);
  30. }
  31. }
  32. }
  33. }

示例结果

image.png
从上面的运行结果可以看出,60,71 , 52,43 , 44,65,5*6。可以看出,输出3,4的是相同的线程,输出0,5的也是相同的线程,输出2,6的也是相同的线程。

线程池注意事项

  • 使用线程池来运行任务时,要求任务是短时间内可以运行完成,以便可以快速将占用的线程放回到线程池中,如果所有任务都是长时间运行,会导致线程池里面没有可用线程
  • 线程池会创建一个合适数量的线程,既能充分利用cpu资源,又能避免线程过多导致的频繁线程切换带来的性能损耗
  • 使用线程池来运行的任务,无法控制具体的运行时间,包含开始和结束,比如无法像之前那样进行线程同步

    下期预告

    那有没有办法让我们不需要考虑线程这些细节,让我们只关注需要运行的任务和任务的运行结果,运行过程由框架来进行调度底层的线程来运行,这正是.net里面的Task要解决的问题,敬请期待。