ProcessorCount 是 SemaphoreSlim 中 maxCount 的合理值吗?

Is ProcessorCount is sensible value for maxCount in SemaphoreSlim?

在我同事的代码中,我注意到这样的代码:

private Task FetchAllKeysFromRedis(List<string> keys, ConcurrentBag<RedisModel> resultsBag, CancellationToken cancellationToken)
{
        var parallelism = Environment.ProcessorCount;
        var semafore = new SemaphoreSlim(initialCount: parallelism, maxCount: parallelism);
        var tasks = new List<Task>();
        foreach (var key in keys)
        {
            cancellationToken.ThrowIfCancellationRequested();
            tasks.Add(FetchFromRedis(key, semafore, resultsBag, cancellationToken));
        }

        return Task.WhenAll(tasks);

List<string> keys 大约包含 1000 个键,FetchFromRedis 方法异步执行 I/O 操作(从 Redis 中获取),所以总结起来它执行了大约 1000 个 I/O 操作。

关键部分如下所示:

private async Task FetchFromRedis(string key, SemaphoreSlim semafore, ConcurrentBag<RedisResult> resultsBag, CancellationToken cancellationToken)
{
     await semafore.WaitAsync(cancellationToken);
     try
     {
         var redisResult = await _getRedisResultFromRedis.ExecuteAsync(key, cancellationToken);
         if (redisResult != null)
              resultsBag.Add(redisResult);
      
         finally
         {
            semafore.Release();
         }
      }

我的问题:Environment.ProcessorCount maxCount 对于 SemaphoreSlim 是否明智?因为它会将可以进入临界区的线程数限制在非常低的水平,例如 8 个,并且执行时间会更长?

如果不合理,那么 maxCount 个线程的合理值是多少?

我的看法:因为涉及IO,大部分时间只是等待,不需要处理器时间。因此,将并发限制为核心数量毫无意义。

我同意你的推理,因为这项工作看起来是受延迟或带宽限制的网络请求,将并发限制为 CPU 个核心数没有多大意义。

您可能需要进行一些测试才能找到合适的 maxCount 值。但理想情况下,您应该有一个 self-adapting 适合实际情况的系统。老实说,我不确定最好的设计是什么,但我会看一下 dataflow 看看是否可以提供更好的方法来限制并发。或者至少将值公开为配置,以便稍后可以对其进行调整。

仅在一种情况下使用值 Environment.ProcessorCount 配置并行度是明智的:如果您完全不知道如何配置此设置。在这种情况下,任何值都将是随机和任意的,那么为什么不选择 Environment.ProcessorCount,它以某种方式反映了当前机器的能力?

出现了类似的困境while designing the Parallel.ForEachAsync API, that debuted on .NET 6. After evaluating the available options, the Microsoft API designers chose the Environment.ProcessorCount as the value for the MaxDegreeOfParallelism, when this configuration is not explicitly provided. It's ironic that the synchronous Parallel.ForEach API, for which the Environment.ProcessorCount would be an even more sensible default, it has actually -1 as the default, which means "unlimited" parallelism, or practically limited by the ThreadPool可用性。