You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Parallel.ForEach加锁性能慢,不加锁数据不一致的问题求助

问题根源分析
  1. 共享可变实例的线程冲突:多个并行线程复用同一个ApiFileItems实例,同时修改apiFileItems.relativeFilePath会导致值被覆盖,这就是数据不一致的核心原因。而你“将模型的每个项作为Parallel.ForEach参数时数据正常”,本质是每个线程使用独立参数,没有共享状态冲突。
  2. 非线程安全集合的竞态条件:foundFolderItems应为普通Dictionary,它的TryGetValue和TryAdd并非原子操作。无锁并行时,多线程同时执行“检查-添加”逻辑会出现竞态,导致重复添加或漏加,造成数据丢失。
  3. 过度锁导致性能暴跌:直接对ContentNode或rootNode加锁,相当于把并行执行强制改成串行,完全丧失了并行的性能优势,这是性能极慢的关键原因。
优化解决方案

1. 避免共享可变实例

每个并行线程创建独立的ApiFileItems副本,仅复制hostName、publicationId这类不变属性,线程内部修改自己的副本属性,彻底消除线程间的属性覆盖问题。

2. 替换为线程安全集合

将foundFolderItems替换为ConcurrentDictionary<string, bool>,它的TryGetValue和TryAdd方法是原子操作,无需额外加锁就能保证线程安全。

3. 移除不必要的锁

JToken(包括JArray)的读取操作是线程安全的,你原来的锁完全多余,直接移除即可恢复并行性能。

4. 合理设置并行度

不要硬编码MaxDegreeOfParallelism = 4,可以使用默认值(等于CPU核心数),或根据实际CPU负载动态调整,让系统自动适配最优并行规模。

修改后的代码示例
// 替换普通Dictionary为线程安全的ConcurrentDictionary
private static readonly ConcurrentDictionary<string, bool> foundFolderItems = new ConcurrentDictionary<string, bool>();

private static void ParallelExecution(JArray ContentNode, ApiFileItems apiFileItems, ApiTypeItem apiTypeItem)
{
    // 移除不必要的锁,直接并行遍历
    Parallel.ForEach(ContentNode.Values(), new ParallelOptions(), (rootNode) =>
    {
        if (rootNode.HasValues)
        {
            ParallelRootItemExecution(rootNode, apiFileItems, apiTypeItem);
        }
        else
        {
            // Log the message
        }
    });
}

private static void ParallelRootItemExecution(JToken rootNode, ApiFileItems apiFileItems, ApiTypeItem apiTypeItem)
{
    Parallel.ForEach<JToken>(rootNode.Values(), new ParallelOptions(), (metaNode) =>
    {
        // 创建线程独立的ApiFileItems副本,避免共享修改
        var threadLocalApiFileItems = new ApiFileItems
        {
            hostName = apiFileItems.hostName,
            publicationId = apiFileItems.publicationId,
            relativeGroupUrl = apiFileItems.relativeGroupUrl
            // 复制其他需要的不变属性
        };

        threadLocalApiFileItems.relativeFilePath = metaNode["valueString"].ToString();

        // 使用ConcurrentDictionary的原子操作,无需额外加锁
        if (!foundFolderItems.TryGetValue(threadLocalApiFileItems.relativeFilePath, out _))
        {
            if (foundFolderItems.TryAdd(threadLocalApiFileItems.relativeFilePath, true))
            {
                // 递归调用时传递线程本地的副本
                ParallelExecution(
                    JArray.Parse(string.Format(threadLocalApiFileItems.relativeGroupUrl, 
                        threadLocalApiFileItems.hostName, 
                        threadLocalApiFileItems.publicationId, 
                        threadLocalApiFileItems.relativeFilePath)), 
                    threadLocalApiFileItems, 
                    apiTypeItem);
            }
        }
    });
}
额外优化建议
  • 若递归深度较大,建议限制递归层级,避免栈溢出。
  • 对于小批量数据,可使用Partitioner.Create优化并行遍历的分区效率。
  • 监控CPU使用率,若CPU未跑满可适当提高MaxDegreeOfParallelism;若CPU过载则降低该值。

内容的提问来源于stack exchange,提问作者Samuel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 16:30:58