.net 并行foreach循环中缺少日志语句

.net 并行foreach循环中缺少日志语句,.net,c#-4.0,parallel.foreach,parallel.for,.net,C# 4.0,Parallel.foreach,Parallel.for,我使用的是并行foreach/for循环,在特定情况下,我需要使用嵌套的并行foreach/for循环。当我试图打印集合中的值时,有时控制台语句没有打印出来,这是不一致的。请参阅下面的代码片段 Parallel.For(0, RunModuleConfigVariables.Count, new ParallelOptions { MaxDegreeOfParallelism = 3 }, index => { string log = null;

我使用的是并行foreach/for循环,在特定情况下,我需要使用嵌套的并行foreach/for循环。当我试图打印集合中的值时,有时控制台语句没有打印出来,这是不一致的。请参阅下面的代码片段

Parallel.For(0, RunModuleConfigVariables.Count, new ParallelOptions { MaxDegreeOfParallelism = 3 }, index => {
                string log = null;
                int count = 0;
                log += "Module Name " + RunModuleConfigVariables.Keys.ElementAt(index) + " thread: " + Thread.CurrentThread.ManagedThreadId + "\n";
                Parallel.ForEach(RunModuleConfigVariables[RunModuleConfigVariables.Keys.ElementAt(index)], new ParallelOptions { MaxDegreeOfParallelism = 10 }, eachendpoint => {

                    log += "\t" + count + " Endpoint Name " + eachendpoint + "\n";
                    count++;
                });
                Console.WriteLine(log);
            });
收藏:

Module Name Module_1 thread: 4
        0 Endpoint Name Module_1_Endpoint_2
        1 Endpoint Name Module_1_Endpoint_1
        2 Endpoint Name Module_1_Endpoint_4
        3 Endpoint Name Module_1_Endpoint_5
        4 Endpoint Name Module_1_Endpoint_6
        5 Endpoint Name Module_1_Endpoint_7
        6 Endpoint Name Module_1_Endpoint_8
        18 Endpoint Name Module_1_Endpoint_9

Module Name Module_3 thread: 5
        0 Endpoint Name Module_3_Endpoint_1

Module Name Module_2 thread: 1
        0 Endpoint Name Module_2_Endpoint_2
        1 Endpoint Name Module_2_Endpoint_3
        2 Endpoint Name Module_2_Endpoint_1
集合类型为ConcurrentDictionary()

实际输出:

Module Name Module_1 thread: 4
        0 Endpoint Name Module_1_Endpoint_2
        1 Endpoint Name Module_1_Endpoint_1
        2 Endpoint Name Module_1_Endpoint_4
        3 Endpoint Name Module_1_Endpoint_5
        4 Endpoint Name Module_1_Endpoint_6
        5 Endpoint Name Module_1_Endpoint_7
        6 Endpoint Name Module_1_Endpoint_8
        18 Endpoint Name Module_1_Endpoint_9

Module Name Module_3 thread: 5
        0 Endpoint Name Module_3_Endpoint_1

Module Name Module_2 thread: 1
        0 Endpoint Name Module_2_Endpoint_2
        1 Endpoint Name Module_2_Endpoint_3
        2 Endpoint Name Module_2_Endpoint_1
预期输出:(顺序不必相同)


注意:输出不一致。有时能看到所有子孩子,有时不能。我如何理解这一点,以及如何克服这一点?

问题是您的变量
log
被多个线程分配。在尝试写入之前,需要将其锁定

Parallel.For(0, RunModuleConfigVariables.Count, new ParallelOptions { MaxDegreeOfParallelism = 3 }, index => {
                string log = null;
                int count = 0;
                log += "Module Name " + RunModuleConfigVariables.Keys.ElementAt(index) + " thread: " + Thread.CurrentThread.ManagedThreadId + "\n";
                object locker = new object();
                Parallel.ForEach(RunModuleConfigVariables[RunModuleConfigVariables.Keys.ElementAt(index)], new ParallelOptions { MaxDegreeOfParallelism = 10 }, eachendpoint => {
                    lock(locker)
                        log += "\t" + (count++) + " Endpoint Name " + eachendpoint + "\n";
                });
                Console.WriteLine(log);
            });

问题是您的变量
log
被多个线程分配给了。在尝试写入之前,需要将其锁定

Parallel.For(0, RunModuleConfigVariables.Count, new ParallelOptions { MaxDegreeOfParallelism = 3 }, index => {
                string log = null;
                int count = 0;
                log += "Module Name " + RunModuleConfigVariables.Keys.ElementAt(index) + " thread: " + Thread.CurrentThread.ManagedThreadId + "\n";
                object locker = new object();
                Parallel.ForEach(RunModuleConfigVariables[RunModuleConfigVariables.Keys.ElementAt(index)], new ParallelOptions { MaxDegreeOfParallelism = 10 }, eachendpoint => {
                    lock(locker)
                        log += "\t" + (count++) + " Endpoint Name " + eachendpoint + "\n";
                });
                Console.WriteLine(log);
            });
我怎么能理解呢

并行处理意味着多个线程同时执行任务。这会导致各种奇怪的事情,你必须小心

以这一行为例:

count++;
这条C#指令实际上代表了多种操作:

  • count
    变量中的值从内存加载到处理器中
  • 1
    添加到加载到处理器的值中
  • 将新值存储到
    count
    变量的内存位置
  • 现在想象两个线程同时执行这三条指令。他们两人都有可能在完成第3步之前完成第1步。这意味着如果
    count
    从零开始,两个线程现在都将
    count
    设置为
    1
    ,这不是您想要的

    这一行在读取
    log
    和写入
    之间有更多的步骤:

    log += "\t" + count + " Endpoint Name " + eachendpoint + "\n";
    
    因此,您会发现一个线程覆盖(而不是添加)另一个线程已经写入的值的频率要高得多。这就是你注意到的行为

    。。。让我知道,我们能做些什么来克服这个问题

    首先,尽可能避免并行处理

    如果使用一个简单的
    foreach
    循环,事情进展得足够快,不要试图优化它们

    如果使用简单的
    foreach
    循环速度不够快,请找出原因。大多数情况下,这是因为I/O操作(磁盘或网络访问)。在这些情况下,使用异步任务的并发执行,而不是多线程。见和

    如果您正在执行需要CPU能力的操作,并且确实需要它们并行运行,以压缩额外的性能,请尽量避免更改每个操作中的状态(例如,为共享变量设置值,如
    count++
    )。一个很好的策略是命令/查询分离,您可以在不可变的数据结构上进行并行处理以生成“答案”,然后使用这些答案进行必须在同一线程上进行的更改。下面是代码中可能出现的情况:

    var logs = RunModuleConfigVariables
        .AsParallel()
        .WithDegreeOfParallelism(3)
        .Select(e =>
            "Module Name " + e.Key + " thread: " + Thread.CurrentThread.ManagedThreadId + "\n"
                + string.Join("\n",
                    e.Value
                        .AsParallel()
                        .WithDegreeOfParallelism(10)
                        .Select((eachendpoint, index) => "\t" + index + " Endpoint Name " + eachendpoint)
    
        ));
    
    Console.WriteLine(string.Join("\n", logs));
    
    
    最后,如果您绝对必须并行更改状态,则需要花时间了解锁、互斥锁、并发集合和其他类似工具,并确保您仅在并行上下文中使用线程安全方法,以确保您的操作“正确”

    这可能会导致类似的情况:

    Parallel.ForEach(RunModuleConfigVariables, new ParallelOptions { MaxDegreeOfParallelism = 3 }, pair =>
    {
        Console.WriteLine("Module Name " + pair.Key + " thread: " + Thread.CurrentThread.ManagedThreadId);
        var count = 0;
        Parallel.ForEach(pair.Value, new ParallelOptions { MaxDegreeOfParallelism = 10 }, eachendpoint =>
        {
            var thisCount = Interlocked.Increment(ref count);
            Console.WriteLine("\t" + thisCount + " Endpoint Name " + eachendpoint + "\n");
        });
    });
    
    我怎么能理解呢

    并行处理意味着多个线程同时执行任务。这会导致各种奇怪的事情,你必须小心

    以这一行为例:

    count++;
    
    这条C#指令实际上代表了多种操作:

  • count
    变量中的值从内存加载到处理器中
  • 1
    添加到加载到处理器的值中
  • 将新值存储到
    count
    变量的内存位置
  • 现在想象两个线程同时执行这三条指令。他们两人都有可能在完成第3步之前完成第1步。这意味着如果
    count
    从零开始,两个线程现在都将
    count
    设置为
    1
    ,这不是您想要的

    这一行在读取
    log
    和写入
    之间有更多的步骤:

    log += "\t" + count + " Endpoint Name " + eachendpoint + "\n";
    
    因此,您会发现一个线程覆盖(而不是添加)另一个线程已经写入的值的频率要高得多。这就是你注意到的行为

    。。。让我知道,我们能做些什么来克服这个问题

    首先,尽可能避免并行处理

    如果使用一个简单的
    foreach
    循环,事情进展得足够快,不要试图优化它们

    如果使用简单的
    foreach
    循环速度不够快,请找出原因。大多数情况下,这是因为I/O操作(磁盘或网络访问)。在这些情况下,使用异步任务的并发执行,而不是多线程。见和

    如果您正在执行需要CPU能力的操作,并且确实需要它们并行运行,以压缩额外的性能,请尽量避免更改每个操作中的状态(例如,为共享变量设置值,如
    count++
    )。一个很好的策略是命令/查询分离,您可以在不可变的数据结构上进行并行处理以生成“答案”,然后使用这些答案进行必须在同一线程上进行的更改。下面是代码中可能出现的情况:

    var logs = RunModuleConfigVariables
        .AsParallel()
        .WithDegreeOfParallelism(3)
        .Select(e =>
            "Module Name " + e.Key + " thread: " + Thread.CurrentThread.ManagedThreadId + "\n"
                + string.Join("\n",
                    e.Value
                        .AsParallel()
                        .WithDegreeOfParallelism(10)
                        .Select((eachendpoint, index) => "\t" + index + " Endpoint Name " + eachendpoint)
    
        ));
    
    Console.WriteLine(string.Join("\n", logs));
    
    
    最后,如果您必须并行更改状态,则需要花时间了解锁、互斥锁、并发集合和o