【问题标题】:Bad SemaphoreSlim perfomance using a lot of semaphoresBad SemaphoreSlim 性能使用大量信号量
【发布时间】:2020-01-29 11:00:22
【问题描述】:

当我为每个操作使用SemaphoreSlim 并行运行大量操作时,它们的调用并没有预期的那么快。

这里是代码

  var sw = new Stopwatch();
  sw.Start();

  for (int i = 0; i < 50; i++) {
    int localI = i;
    Task.Run(async () => {
       var semaphore = new SemaphoreSlim(1, 1);
       await semaphore.WaitAsync();
       Thread.Sleep(1000);
       counter++;
       semaphore.Release();
       Debug.WriteLine($"{localI} - {sw.ElapsedMilliseconds}");
     });
  }

  Thread.Sleep(5000);

这是输出:

2 - 1015
0 - 1015
1 - 1015
3 - 2053
4 - 2053
5 - 2053
6 - 2120
7 - 3009
8 - 3064
9 - 3066
10 - 3068
11 - 3134
12 - 4011
13 - 4016
14 - 4070
15 - 4071
16 - 4073
17 - 4140

有人可以解释为什么他们没有在大约 1 秒内被调用吗?

【问题讨论】:

  • 在创建 SemaphoreSlim 之前添加一个 Debug.WriteLine 并观察结果
  • 顺便说一句,您确实没有看到 SemaphoreSlim 的性能,而是看到 TaskScheduler 的工作方式(添加更多的 WorkerThread);o)
  • 您正在看到 Thread.Sleep() 如何使用处于活动状态但未取得进展的线程来破坏线程池。考虑await Task.Delay(1000);。除非 Sleep() 应该模拟正在完成的实际工作,代码燃烧约 90 亿条指令,否则这正是你想要避免让机器着火的原因。
  • @HansPassant 成功了,谢谢!但我认为 50 号对于线程数来说很小。您能否详细解释一下这种行为?你可以写成答案
  • 50 仅在您拥有一台具有 50 个处理器内核的机器时很小。可以拥有,但不便宜。你的机器有 4 个,这是相当标准的。所以实际上它是在模拟 4*9 = 360 亿条指令的执行。您要求它执行 50*9 = 4500 亿条指令。这不可避免地需要超过一秒钟的时间。请记住,这是一项巨大的工作,Sleep(1000) 是准确模拟的可能性非常小。

标签: c# .net multithreading asynchronous semaphore


【解决方案1】:

您看到的是有限的线程池注入率。它与SemaphoreSlim 甚至async 无关,因为所有发布的代码实际上都是同步的。

在您的机器上,三个线程能够立即运行。线程池发现它还有其他工作要做(47 个其他项目已经排队)。所以它稍等片刻,然后注入另一个线程。下一组工作使用四个线程。线程池仍然是“落后”的,所以它会稍等片刻,然后再注入另一个线程,等等。

上面描述的“稍等”部分是有限的线程池注入率。线程池必须稍等片刻,否则当它得到更多工作时,它会立即创建一堆线程,然后在工作完成时将其处理掉。因此,为了提高效率并防止这种“线程抖动”,线程池在创建新线程之前会等待一段时间。

【讨论】:

    猜你喜欢
    • 2012-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-30
    • 2022-08-04
    相关资源
    最近更新 更多