.NET 并行循环在单 CPU 容器中可能变成串行执行

BestBlog编程类文章 · 软件开发

📌 One-Sentence Summary 本文展示了 .NET 的 Parallel.ForEachAsync 在单 CPU 容器中默认采用单线程执行,导致性能意外下降,并提出按依赖项设置并发预算,以避免服务过载和请求被拒绝。 📝 Summary 本文探讨了一个性能问题:.NET 的 Parallel.ForEachAsync 在单 CPU 容器中会变成串行执行,处理 200 条记录的耗时从 42.1 秒增至 83.6 秒。根本原因在于,受限容器中的 ProcessorCount 返回 1,从而限制了 MaxDegreeOfParallelism。作者介绍了一个 DependencyBudgets 辅助类,为每个外部服务(数据增强 API 和系统记录源)分别设置并发上限,避免依赖项因速率限制而过载。对比四种策略(C1-C4)的实验室测试表明,按依赖项设置并发预算(C4)能够消除 HTTP 503 拒绝,同时保持相近的吞吐量,凸显了在容器化环境中实施明确并发控制的重要性。 💡 Main Points Parallel.ForEachAsync 默认使用 Environment.ProcessorCount,而单 CPU 容器中的该值会变为 1 在 Linux/macOS 和 Windows 上,自 .NET 6 起,ProcessorCount 返回逻辑处理器数量、CPU 亲和性和利用率限制向上取整后的值中的最小值。将容器限制为 1 个 CPU 会使 ProcessorCount=1,导致并行循环串行执行,而非并发执行。 按依赖项设置并发预算可以避免服务过载和请求被拒绝 DependencyBudgets 辅助类为每个外部服务使用独立的 SemaphoreSlim 实例,从而分别设置并发上限。这可以避免某个缓慢或受速率限制的服务阻塞所有并发操作。C4 的测试结果表明,该方案的 HTTP 503 拒绝数为 0,而采用单一限制的方案(C2)出现了 64 次拒绝。 明确配置并进行监控,对确保容器化工作负载可靠至关重要 作者建议将并发限制设为明确的配置,并在启动时进行验证;监控每个依赖项的进行中调用数;同时在实际 CPU 限制下开展测试。这些做法有助于避免容器资源发生变化时出现无声的性能下降。 💬 Key Quotes 在我的实验室中,将容器的 CPU 限制从 2 个 CPU 改为 1 个后,处理 200 条记录所需的时间几乎翻倍:从 42.1 秒增至 83.6 秒。 不使用 ParallelOptions 的 Parallel.ForEachAsync,最多只允许 Environment.ProcessorCount 个操作并发执行。 C4 按配置最多只提供了 6 个并发操作,而且每次运行中,系统记录源都接受了全部 200 次写入。 如果可靠写入比更快完成批处理更重要,我愿意接受健康吞吐量大约降低 11--14%。 📊 Article Meta AI Screening: 84 Source: Stack Overflow Blog Author: Guilherme Appel Category: 软件编程 Language: 英文 Read Time: 8 min Word Count: 1827 Tags: 编程与工程 , 后端开发 , 云原生 / DevOps , 性能优化 , 分布式系统 Read Full Article

查看原文