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

sqserrrh  于 2023-08-08  发布在  .NET
关注(0)|答案(2)|浏览(115)

我使用的是并行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);
            });

字符串

收藏:

集合类型为 ConcurrentDictionary<string,HashSet>()

RunModuleConfigVariables:
        {
      "Module_1": [
        "Module_1_Endpoint_1",
        "Module_1_Endpoint_2",
        "Module_1_Endpoint_3",
        "Module_1_Endpoint_4",
        "Module_1_Endpoint_5",
        "Module_1_Endpoint_6",
        "Module_1_Endpoint_7",
        "Module_1_Endpoint_8",
        "Module_1_Endpoint_9",
        "Module_1_Endpoint_10",
        "Module_1_Endpoint_11",
        "Module_1_Endpoint_12",
        "Module_1_Endpoint_13",
        "Module_1_Endpoint_14",
        "Module_1_Endpoint_15",
        "Module_1_Endpoint_16",
        "Module_1_Endpoint_17",
        "Module_1_Endpoint_18",
        "Module_1_Endpoint_19"
      ],
      "Module_2": [
        "Module_2_Endpoint_1",
        "Module_2_Endpoint_2",
        "Module_2_Endpoint_3"
      ],
      "Module_3": [
        "Module_3_Endpoint_1"
      ]
    }

实际产量:

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

预期输出:(顺序不必相同)

Module Name Module_1 thread: 5
        0 Endpoint Name Module_1_Endpoint_2
        1 Endpoint Name Module_1_Endpoint_3
        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
        7 Endpoint Name Module_1_Endpoint_9
        8 Endpoint Name Module_1_Endpoint_10
        9 Endpoint Name Module_1_Endpoint_11
        10 Endpoint Name Module_1_Endpoint_12
        11 Endpoint Name Module_1_Endpoint_13
        12 Endpoint Name Module_1_Endpoint_14
        13 Endpoint Name Module_1_Endpoint_15
        14 Endpoint Name Module_1_Endpoint_16
        15 Endpoint Name Module_1_Endpoint_17
        16 Endpoint Name Module_1_Endpoint_18
        17 Endpoint Name Module_1_Endpoint_19
        18 Endpoint Name Module_1_Endpoint_1

Module Name Module_2 thread: 4
        0 Endpoint Name Module_2_Endpoint_2
        1 Endpoint Name Module_2_Endpoint_3
        2 Endpoint Name Module_2_Endpoint_1

Module Name Module_3 thread: 1
        0 Endpoint Name Module_3_Endpoint_1


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

euoag5mw

euoag5mw1#

我怎么能理解呢?
并行处理意味着多个线程同时做事情。这会导致各种奇怪的事情,你必须小心。
考虑这一行:

count++;

字符串
这一条C#指令实际上代表了多个操作:
1.将count变量的值从内存加载到处理器中。
1.将1添加到加载到处理器中的值。
1.将新值存储到count变量的内存位置。
现在想象两个线程同时执行这三条指令。有一个微小的可能性,他们都将完成步骤1之前,任何完成步骤3。这意味着如果count从零开始,两个线程现在都会将count设置为1,这不是您想要的。
这一行在读取log的点和写入log的点之间有更多的步骤:

log += "\t" + count + " Endpoint Name " + eachendpoint + "\n";


因此,您会发现一个线程更频繁地覆盖(而不是添加)另一个线程已经写入的值。这就是你注意到的行为。
......让我知道,可以做些什么来克服这一点。
首先,尽可能避免并行处理。
如果使用简单的foreach循环就能让事情变得足够快,那么就不要试图优化它们。
如果使用简单的foreach循环运行速度不够快,请找出原因。大多数情况下,这是由于I/O操作(磁盘或网络访问)。在这些情况下,使用异步任务的并发执行,而不是多线程。参见https://stackoverflow.com/a/14130314/120955What is the difference between asynchronous programming and multithreading?
如果您正在执行需要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));


最后,如果你必须并行地改变状态,你需要花时间学习锁、互斥锁、并发集合、atomic operations和其他类似的工具,并确保你只在并行上下文中使用线程安全的方法,以确保你做的是“正确的”。
可能会导致这样的结果:

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");
    });
});

rkue9o1l

rkue9o1l2#

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

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);
            });

字符串

相关问题