找回密码
 立即注册
首页 业界区 业界 《手搓》线程池优化的追求

《手搓》线程池优化的追求

奄蜊 3 天前
一、先回顾一下以前《手搓》线程池Case



  • ConcurrencyLevel设置为10
  • 并发实现了完美的指数递进关系
  • 当时内心还是得到了很大的满足
  • 第一批01:58:55.832是1个并发
  • 第二批01:58:55.944是2个并发
  • 第三批01:58:56.054是4个并发
  • 第四批01:58:56.165是8个并发
  • 01:58:56.276及以后一段时间达到并发上限10个
  • 参考笔者博文《手搓》线程池
  1. var options = new ReduceOptions { ConcurrencyLevel = 10 };
  2. var scheduler = new ConcurrentTaskScheduler(options);
  3. var factory = new TaskFactory(scheduler);
  4. var jobService = new ConcurrentJobService(scheduler, options);
  5. jobService.Start();
  6. Start(factory);
  7. private void Start(TaskFactory factory)
  8. {
  9.     for (int i = 1; i < 10; i++)
  10.     {
  11.         for (int j = 1; j < 10; j++)
  12.         {
  13.             int a = i, b = j;
  14.             factory.StartNew(() => Multiply(a, b));
  15.         }
  16.     }
  17. }
  18. public int Multiply(int a, int b)
  19. {
  20.     var result = a * b;
  21.     _output.WriteLine($"{a} x {b} = {result},{DateTime.Now:HH:mm:ss.fff}");
  22.     Thread.Sleep(100);
  23.     return result;
  24. }
  25. // 1 x 1 = 1,01:58:55.832
  26. // 1 x 2 = 2,01:58:55.944
  27. // 1 x 3 = 3,01:58:55.944
  28. // 1 x 4 = 4,01:58:56.054
  29. // 1 x 5 = 5,01:58:56.054
  30. // 1 x 6 = 6,01:58:56.054
  31. // 1 x 7 = 7,01:58:56.054
  32. // 1 x 8 = 8,01:58:56.165
  33. // 1 x 9 = 9,01:58:56.165
  34. // 2 x 1 = 2,01:58:56.165
  35. // 2 x 3 = 6,01:58:56.165
  36. // 2 x 2 = 4,01:58:56.165
  37. // 2 x 4 = 8,01:58:56.165
  38. // 2 x 5 = 10,01:58:56.165
  39. // 2 x 6 = 12,01:58:56.165
  40. // 2 x 8 = 16,01:58:56.276
  41. // 2 x 9 = 18,01:58:56.276
  42. // 3 x 2 = 6,01:58:56.276
  43. // 2 x 7 = 14,01:58:56.276
  44. // 3 x 3 = 9,01:58:56.276
  45. // 3 x 1 = 3,01:58:56.276
  46. // 3 x 4 = 12,01:58:56.276
  47. // 3 x 5 = 15,01:58:56.276
  48. // 3 x 6 = 18,01:58:56.277
  49. // 3 x 7 = 21,01:58:56.277
  50. // 4 x 1 = 4,01:58:56.388
  51. // 3 x 8 = 24,01:58:56.388
  52. // 3 x 9 = 27,01:58:56.388
  53. // 4 x 2 = 8,01:58:56.388
  54. // 4 x 6 = 24,01:58:56.388
  55. // 4 x 3 = 12,01:58:56.388
  56. // 4 x 5 = 20,01:58:56.388
  57. // 4 x 8 = 32,01:58:56.388
  58. // 4 x 4 = 16,01:58:56.388
  59. // 4 x 7 = 28,01:58:56.388
  60. // 5 x 8 = 40,01:58:56.500
  61. // 5 x 4 = 20,01:58:56.500
  62. // 5 x 9 = 45,01:58:56.500
  63. // 5 x 2 = 10,01:58:56.500
  64. // 5 x 7 = 35,01:58:56.500
  65. // 5 x 6 = 30,01:58:56.500
  66. // 4 x 9 = 36,01:58:56.500
  67. // 5 x 5 = 25,01:58:56.500
  68. // 5 x 1 = 5,01:58:56.500
  69. // 5 x 3 = 15,01:58:56.500
  70. // 6 x 5 = 30,01:58:56.612
  71. // 6 x 2 = 12,01:58:56.612
  72. // 6 x 1 = 6,01:58:56.612
  73. // 6 x 3 = 18,01:58:56.612
  74. // 6 x 6 = 36,01:58:56.612
  75. // 6 x 7 = 42,01:58:56.612
  76. // 6 x 4 = 24,01:58:56.612
  77. // 6 x 8 = 48,01:58:56.612
  78. // 6 x 9 = 54,01:58:56.612
  79. // 7 x 1 = 7,01:58:56.612
  80. // 7 x 2 = 14,01:58:56.724
  81. // 8 x 1 = 8,01:58:56.724
  82. // 7 x 4 = 28,01:58:56.724
  83. // 7 x 7 = 49,01:58:56.724
  84. // 7 x 5 = 35,01:58:56.724
  85. // 8 x 2 = 16,01:58:56.724
  86. // 7 x 8 = 56,01:58:56.724
  87. // 7 x 6 = 42,01:58:56.724
  88. // 7 x 9 = 63,01:58:56.724
  89. // 7 x 3 = 21,01:58:56.724
  90. // 8 x 5 = 40,01:58:56.836
  91. // 8 x 8 = 64,01:58:56.836
  92. // 9 x 1 = 9,01:58:56.836
  93. // 9 x 2 = 18,01:58:56.836
  94. // 8 x 7 = 56,01:58:56.836
  95. // 8 x 9 = 72,01:58:56.836
  96. // 8 x 3 = 24,01:58:56.836
  97. // 8 x 6 = 48,01:58:56.836
  98. // 9 x 3 = 27,01:58:56.836
  99. // 8 x 4 = 32,01:58:56.836
  100. // 9 x 5 = 45,01:58:56.948
  101. // 9 x 7 = 63,01:58:56.948
  102. // 9 x 4 = 36,01:58:56.948
  103. // 9 x 6 = 54,01:58:56.948
  104. // 9 x 9 = 81,01:58:56.948
  105. // 9 x 8 = 72,01:58:56.948
复制代码
二、短暂的快乐被异步并发测试给打破了



  • ConcurrencyLevel设置为4
  • 一开始就是4个清晰可见的并发
  • 一发入魂,开局即是高潮
  • 第一感觉是bug
  • 第二感觉是偶发事件
  • 笔者多次测试,并反复Review代码,都没发现其中的蹊跷
  • 参考笔者博文《手搓》TaskFactory带你安全的起飞
  1. var options = new ReduceOptions { ConcurrencyLevel = 4 };
  2. var factory = new ConcurrentTaskFactory(options);
  3. _output.WriteLine($"begin {DateTime.Now:HH:mm:ss.fff}");
  4. Stopwatch sw = Stopwatch.StartNew();
  5. List<Task<Product>> tasks = new(100);
  6. for (int i = 0; i < 100; i++)
  7. {
  8.     var id = i;
  9.     var task = factory.StartTask(() => GetProductAsync(id));
  10.     tasks.Add(task);
  11. }
  12. var products = await Task.WhenAll(tasks);
  13. sw.Stop();
  14. _output.WriteLine($"end {DateTime.Now:HH:mm:ss.fff}, Elapsed {sw.ElapsedMilliseconds}");
  15. Assert.NotNull(products);
  16. Assert.Equal(100, products.Length);
  17. // begin 10:20:45.317
  18. // Thread36 GetProductAsync(0),10:20:45.487
  19. // Thread11 GetProductAsync(2),10:20:45.487
  20. // Thread8 GetProductAsync(3),10:20:45.487
  21. // Thread35 GetProductAsync(1),10:20:45.487
  22. // Thread11 GetProductAsync(6),10:20:45.614
  23. // Thread35 GetProductAsync(4),10:20:45.614
  24. // Thread8 GetProductAsync(7),10:20:45.614
  25. // Thread36 GetProductAsync(5),10:20:45.614
  26. // Thread35 GetProductAsync(9),10:20:45.742
  27. // Thread8 GetProductAsync(8),10:20:45.742
  28. // Thread41 GetProductAsync(11),10:20:45.742
  29. // Thread37 GetProductAsync(10),10:20:45.742
  30. // Thread37 GetProductAsync(12),10:20:45.869
  31. // Thread8 GetProductAsync(14),10:20:45.869
  32. // Thread11 GetProductAsync(13),10:20:45.869
  33. // Thread41 GetProductAsync(15),10:20:45.869
  34. // Thread8 GetProductAsync(18),10:20:45.997
  35. // Thread35 GetProductAsync(17),10:20:45.997
  36. // Thread41 GetProductAsync(19),10:20:45.997
  37. // Thread11 GetProductAsync(16),10:20:45.997
  38. // Thread11 GetProductAsync(20),10:20:46.125
  39. // Thread8 GetProductAsync(22),10:20:46.125
  40. // Thread35 GetProductAsync(21),10:20:46.125
  41. // Thread41 GetProductAsync(23),10:20:46.125
  42. // Thread37 GetProductAsync(27),10:20:46.253
  43. // Thread41 GetProductAsync(26),10:20:46.253
  44. // Thread35 GetProductAsync(25),10:20:46.253
  45. // Thread11 GetProductAsync(24),10:20:46.253
  46. // Thread35 GetProductAsync(29),10:20:46.381
  47. // Thread41 GetProductAsync(28),10:20:46.381
  48. // Thread11 GetProductAsync(31),10:20:46.381
  49. // Thread37 GetProductAsync(30),10:20:46.381
  50. // Thread8 GetProductAsync(32),10:20:46.507
  51. // Thread37 GetProductAsync(34),10:20:46.507
  52. // Thread41 GetProductAsync(35),10:20:46.507
  53. // Thread11 GetProductAsync(33),10:20:46.507
  54. // Thread37 GetProductAsync(39),10:20:46.635
  55. // Thread11 GetProductAsync(37),10:20:46.635
  56. // Thread41 GetProductAsync(36),10:20:46.635
  57. // Thread35 GetProductAsync(38),10:20:46.635
  58. // Thread41 GetProductAsync(40),10:20:46.763
  59. // Thread37 GetProductAsync(41),10:20:46.763
  60. // Thread35 GetProductAsync(42),10:20:46.763
  61. // Thread11 GetProductAsync(43),10:20:46.763
  62. // Thread41 GetProductAsync(47),10:20:46.891
  63. // Thread8 GetProductAsync(44),10:20:46.891
  64. // Thread11 GetProductAsync(46),10:20:46.891
  65. // Thread37 GetProductAsync(45),10:20:46.891
  66. // Thread37 GetProductAsync(51),10:20:47.018
  67. // Thread8 GetProductAsync(49),10:20:47.018
  68. // Thread41 GetProductAsync(48),10:20:47.018
  69. // Thread11 GetProductAsync(50),10:20:47.018
  70. // Thread41 GetProductAsync(55),10:20:47.146
  71. // Thread11 GetProductAsync(54),10:20:47.146
  72. // Thread8 GetProductAsync(52),10:20:47.146
  73. // Thread37 GetProductAsync(53),10:20:47.146
  74. // Thread11 GetProductAsync(59),10:20:47.274
  75. // Thread8 GetProductAsync(58),10:20:47.274
  76. // Thread37 GetProductAsync(56),10:20:47.274
  77. // Thread41 GetProductAsync(57),10:20:47.274
  78. // Thread41 GetProductAsync(62),10:20:47.402
  79. // Thread11 GetProductAsync(63),10:20:47.402
  80. // Thread37 GetProductAsync(60),10:20:47.402
  81. // Thread8 GetProductAsync(61),10:20:47.402
  82. // Thread41 GetProductAsync(66),10:20:47.530
  83. // Thread88 GetProductAsync(64),10:20:47.530
  84. // Thread35 GetProductAsync(65),10:20:47.530
  85. // Thread11 GetProductAsync(67),10:20:47.530
  86. // Thread11 GetProductAsync(68),10:20:47.658
  87. // Thread41 GetProductAsync(70),10:20:47.658
  88. // Thread8 GetProductAsync(69),10:20:47.658
  89. // Thread35 GetProductAsync(71),10:20:47.658
  90. // Thread41 GetProductAsync(74),10:20:47.786
  91. // Thread95 GetProductAsync(75),10:20:47.786
  92. // Thread35 GetProductAsync(73),10:20:47.786
  93. // Thread88 GetProductAsync(72),10:20:47.786
  94. // Thread95 GetProductAsync(78),10:20:47.914
  95. // Thread41 GetProductAsync(77),10:20:47.914
  96. // Thread88 GetProductAsync(79),10:20:47.914
  97. // Thread8 GetProductAsync(76),10:20:47.914
  98. // Thread95 GetProductAsync(80),10:20:48.042
  99. // Thread41 GetProductAsync(83),10:20:48.042
  100. // Thread8 GetProductAsync(82),10:20:48.042
  101. // Thread35 GetProductAsync(81),10:20:48.042
  102. // Thread95 GetProductAsync(84),10:20:48.170
  103. // Thread35 GetProductAsync(86),10:20:48.170
  104. // Thread41 GetProductAsync(85),10:20:48.170
  105. // Thread8 GetProductAsync(87),10:20:48.170
  106. // Thread11 GetProductAsync(90),10:20:48.297
  107. // Thread88 GetProductAsync(88),10:20:48.297
  108. // Thread8 GetProductAsync(89),10:20:48.297
  109. // Thread35 GetProductAsync(91),10:20:48.297
  110. // Thread11 GetProductAsync(95),10:20:48.425
  111. // Thread8 GetProductAsync(94),10:20:48.425
  112. // Thread41 GetProductAsync(93),10:20:48.425
  113. // Thread35 GetProductAsync(92),10:20:48.425
  114. // Thread41 GetProductAsync(98),10:20:48.553
  115. // Thread35 GetProductAsync(99),10:20:48.553
  116. // Thread8 GetProductAsync(97),10:20:48.553
  117. // Thread88 GetProductAsync(96),10:20:48.553
  118. // end 10:20:48.553, Elapsed 3235
复制代码
三、进一步优化的空间



  • 傍晚在小区边的小湖散步就反复思考异步并发的问题
  • 如果把异步线程等同同步线程,在异步线程未执行完就激活了新的线程
  • 由于激活前这个过程足够快导致线程叠加
  • 造成开局即是高潮的"假象"
1. 《手搓》线程池核心代码



  • 异步时_processor.Run的耗时可以忽略不计
  • 如何能实现同步和异步同样的效果
  1. while (true)
  2. {
  3.     if (_processor.Run())
  4.     {
  5.         _pool.Increment();
  6.     }
  7.     else
  8.     {
  9.         await Task.Delay(_reduceTime, CancellationToken.None)
  10.             .ConfigureAwait(false);
  11.     }
  12.     if (token.IsCancellationRequested)
  13.         break;
  14. }
复制代码
2. 以上想明白后就好优化了



  • 优化后的代码
  • 把_processor.Run()拆分processor.TryTake(out var item)和processor.Run(ref item)
  • 先发现任务,激活线程再执行
  • 有人可能会说,你这代码逻辑有问题啊
  • 发现任务也只是证明本线程有事可干,激活新线程明显浪费资源啊
  • 这点确实应该说一下
  • 现实经验告诉我们,如果发现1只蟑螂,很可能你家已经成为了蟑螂窝
  • 就算激活一个线程没事可干消耗也不大,TryTake返回false就直接走线程回收逻辑了
  1. while (true)
  2. {
  3.     if (processor.TryTake(out var item))
  4.     {
  5.         _pool.Increment();
  6.         processor.Run(ref item);
  7.     }
  8.     else
  9.     {
  10.         await Task.Delay(_reduceTime, CancellationToken.None)
  11.             .ConfigureAwait(false);
  12.     }
  13.     if (token.IsCancellationRequested)
  14.         break;
  15. }
复制代码
3. 优化后再把第一个同步Case重跑一遍



  • ConcurrencyLevel设置为10
  • 同步方法也实现了一发入魂,开局即使高潮
  • 必需承认完美的指数递进关系很有数学美
  • 但笔者做为一个码奴,追求程序性能才是终极目标
  1. var options = new ReduceOptions { ConcurrencyLevel = 10 };
  2. var scheduler = new ConcurrentTaskScheduler(options);
  3. var factory = new TaskFactory(scheduler);
  4. var jobService = new ConcurrentJobService(scheduler, options);
  5. jobService.Start();
  6. Start(factory);
  7. private void Start(TaskFactory factory)
  8. {
  9.     for (int i = 1; i < 10; i++)
  10.     {
  11.         for (int j = 1; j < 10; j++)
  12.         {
  13.             int a = i, b = j;
  14.             factory.StartNew(() => Multiply(a, b));
  15.         }
  16.     }
  17. }
  18. public int Multiply(int a, int b)
  19. {
  20.     var result = a * b;
  21.     _output.WriteLine($"{a} x {b} = {result},{DateTime.Now:HH:mm:ss.fff}");
  22.     Thread.Sleep(100);
  23.     return result;
  24. }
  25. // 1 x 6 = 6,09:15:35.332
  26. // 1 x 3 = 3,09:15:35.332
  27. // 1 x 2 = 2,09:15:35.332
  28. // 1 x 4 = 4,09:15:35.332
  29. // 1 x 5 = 5,09:15:35.332
  30. // 1 x 9 = 9,09:15:35.333
  31. // 1 x 7 = 7,09:15:35.333
  32. // 1 x 8 = 8,09:15:35.333
  33. // 1 x 1 = 1,09:15:35.332
  34. // 2 x 1 = 2,09:15:35.333
  35. // 2 x 6 = 12,09:15:35.443
  36. // 2 x 3 = 6,09:15:35.443
  37. // 2 x 7 = 14,09:15:35.443
  38. // 2 x 4 = 8,09:15:35.443
  39. // 2 x 8 = 16,09:15:35.443
  40. // 2 x 5 = 10,09:15:35.443
  41. // 2 x 2 = 4,09:15:35.443
  42. // 2 x 9 = 18,09:15:35.443
  43. // 3 x 1 = 3,09:15:35.443
  44. // 3 x 2 = 6,09:15:35.444
  45. // 4 x 3 = 12,09:15:35.554
  46. // 3 x 4 = 12,09:15:35.554
  47. // 3 x 5 = 15,09:15:35.554
  48. // 3 x 3 = 9,09:15:35.554
  49. // 3 x 7 = 21,09:15:35.554
  50. // 3 x 9 = 27,09:15:35.554
  51. // 4 x 1 = 4,09:15:35.554
  52. // 3 x 8 = 24,09:15:35.554
  53. // 3 x 6 = 18,09:15:35.554
  54. // 4 x 2 = 8,09:15:35.554
  55. // 4 x 6 = 24,09:15:35.666
  56. // 5 x 4 = 20,09:15:35.666
  57. // 4 x 8 = 32,09:15:35.666
  58. // 4 x 9 = 36,09:15:35.666
  59. // 4 x 7 = 28,09:15:35.666
  60. // 5 x 3 = 15,09:15:35.666
  61. // 4 x 4 = 16,09:15:35.666
  62. // 5 x 2 = 10,09:15:35.666
  63. // 5 x 1 = 5,09:15:35.666
  64. // 4 x 5 = 20,09:15:35.666
  65. // 5 x 6 = 30,09:15:35.777
  66. // 6 x 1 = 6,09:15:35.777
  67. // 6 x 5 = 30,09:15:35.777
  68. // 6 x 4 = 24,09:15:35.777
  69. // 5 x 9 = 45,09:15:35.777
  70. // 5 x 5 = 25,09:15:35.777
  71. // 6 x 2 = 12,09:15:35.777
  72. // 5 x 8 = 40,09:15:35.777
  73. // 5 x 7 = 35,09:15:35.777
  74. // 6 x 3 = 18,09:15:35.777
  75. // 6 x 9 = 54,09:15:35.888
  76. // 6 x 8 = 48,09:15:35.888
  77. // 7 x 1 = 7,09:15:35.888
  78. // 6 x 7 = 42,09:15:35.888
  79. // 6 x 6 = 36,09:15:35.888
  80. // 7 x 6 = 42,09:15:35.888
  81. // 7 x 4 = 28,09:15:35.888
  82. // 7 x 3 = 21,09:15:35.888
  83. // 7 x 5 = 35,09:15:35.888
  84. // 7 x 2 = 14,09:15:35.888
  85. // 7 x 8 = 56,09:15:36.000
  86. // 7 x 7 = 49,09:15:36.000
  87. // 7 x 9 = 63,09:15:36.000
  88. // 8 x 1 = 8,09:15:36.000
  89. // 8 x 7 = 56,09:15:36.000
  90. // 8 x 2 = 16,09:15:36.000
  91. // 8 x 3 = 24,09:15:36.000
  92. // 8 x 5 = 40,09:15:36.000
  93. // 8 x 6 = 48,09:15:36.000
  94. // 8 x 4 = 32,09:15:36.000
  95. // 8 x 8 = 64,09:15:36.112
  96. // 8 x 9 = 72,09:15:36.112
  97. // 9 x 2 = 18,09:15:36.112
  98. // 9 x 1 = 9,09:15:36.112
  99. // 9 x 3 = 27,09:15:36.112
  100. // 9 x 4 = 36,09:15:36.112
  101. // 9 x 5 = 45,09:15:36.112
  102. // 9 x 7 = 63,09:15:36.112
  103. // 9 x 6 = 54,09:15:36.112
  104. // 9 x 8 = 72,09:15:36.112
  105. // 9 x 9 = 81,09:15:36.223
复制代码
4. 线程池执行同步方法开局即使高潮重要吗



  • 当然重要,这就是冷启动的速度问题
  • 如果用TaskFactory对同步方法做并发操作就更显得重要了
  • GetProduct同步方法执行1次耗时0.1秒
  • ConcurrencyLevel设置为7
  • 现在优化后获取7条只要0.1秒
  • 如果冷启动按指数递进关系要至少要0.3秒多
  • 这就快了3倍了
  • 如果ConcurrencyLevel越大,效果就越明显
  1. var options = new ReduceOptions { ConcurrencyLevel = 7 };
  2. var factory = new ConcurrentTaskFactory(options);
  3. _output.WriteLine($"begin {DateTime.Now:HH:mm:ss.fff}");
  4. Stopwatch sw = Stopwatch.StartNew();
  5. List<Task<Product>> tasks = new(7);
  6. for (int i = 0; i < 7; i++)
  7. {
  8.     var id = i;
  9.     var task = factory.StartNew(() => GetProduct(id));
  10.     tasks.Add(task);
  11. }
  12. var products = await Task.WhenAll(tasks);
  13. sw.Stop();
  14. _output.WriteLine($"end {DateTime.Now:HH:mm:ss.fff}, Elapsed {sw.ElapsedMilliseconds}");
  15. internal Product GetProduct(int id)
  16. {
  17.     Thread.Sleep(100);
  18.     _output.WriteLine($"Thread{Environment.CurrentManagedThreadId} GetProductAsync({id}),{DateTime.Now:HH:mm:ss.fff}");
  19.     return new(id);
  20. }
  21. // begin 09:52:02.916
  22. // Thread36 GetProductAsync(5),09:52:03.079
  23. // Thread32 GetProductAsync(1),09:52:03.079
  24. // Thread33 GetProductAsync(2),09:52:03.079
  25. // Thread37 GetProductAsync(6),09:52:03.079
  26. // Thread34 GetProductAsync(3),09:52:03.079
  27. // Thread35 GetProductAsync(4),09:52:03.079
  28. // Thread8 GetProductAsync(0),09:52:03.079
  29. // end 09:52:03.079, Elapsed 162
复制代码
好了,就介绍到这里,更多信息请查看源码库
源码托管地址: https://github.com/donetsoftwork/HandCore.net ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/HandCore.net
如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册