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



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

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<字符串,哈希集>((

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

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

我该如何理解这一点?

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

考虑一下这条线:

count++;

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

  1. count变量的值从内存加载到处理器中
  2. 1加到加载到处理器中的值的值上
  3. 将新值存储到CCD_ 3变量的存储位置中

现在想象两个线程同时执行这三条指令。两者都有可能在完成第3步之前完成第1步。这意味着,如果count从零开始,那么两个线程现在都会将count设置为1,这不是您想要的。

这条线在log的读取点和写入点之间有更多的步骤:

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

因此,您会发现一个线程覆盖(而不是添加到(另一个线程已经写入的值的频率要高得多。这就是你注意到的行为。

。。。让我知道,可以做些什么来克服这一点。

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

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

如果使用一个简单的foreach循环还不够快,请找出原因。大多数情况下,这将是因为I/O操作(磁盘或网络访问(。在这些情况下,使用异步任务的并发执行,而不是多线程。看见https://stackoverflow.com/a/14130314/120955异步编程和多线程之间的区别是什么?。

如果您正在执行需要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");
});
});

问题是多个线程正在为变量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);
});

最新更新