.net 并行foreach循环中缺少日志语句
我使用的是并行foreach/for循环,在特定情况下,我需要使用嵌套的并行foreach/for循环。当我试图打印集合中的值时,有时控制台语句没有打印出来,这是不一致的。请参阅下面的代码片段.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;
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
变量的内存位置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