我有一个Parallel.ForEach()循环,它接受一个URL列表并下载每个URL以进行一些额外的处理。在我的循环之外,我已经声明了一个循环计数器变量,并且在循环体内我使用Interlocked.Increment()认为这将是保持“线程安全”方式的最佳方式,即在执行每个循环交互时增加计数。
int counter = 0;
Parallel.ForEach(urlList, (url, state) =>
{
// various code statments
Interlocked.Increment( ref counter );
Debug.WriteLine(" ......... counter: " + counter);
});
我原本以为我会看到类似的东西:
......... 1
......... 2
......... 3
......... 4
......... 5
.........
.........
......... n
但我得到的是16“......... 0”(这是因为我有一个双核四核计算机,有8个本机核心,但是启用了超线程,总共有16个核心)。然后我将开始看到计数器在大多数情况下正常递增,但有时我会在Debug输出中看到重复或甚至三重计数器值。
使用Parallel.ForEach()计算循环迭代的最佳方法是什么?谢谢你的任何建议。
答案 0 :(得分:14)
Interlocked.Increment将返回递增的值。
所以,
int counter = 0;
Parallel.ForEach(urlList, (url, state) =>
{
// various code statments
var counterNow = Interloced.Increment( ref counter );
Debug.WriteLine(" ......... counter: " + counterNow);
});
应该在递增时返回计数器值。
编辑,很久以后:
作为解释:
当您在多处理器/多核计算机上运行并具有多个应用程序线程时,您需要注意,您看到的变量值可能无法反映该变量的实际当前状态。 这是因为对于每个CPU或骰子或套接字,可能有许多单独的缓存,并且您的线程可以读取缓存的值(保存命中以读取主内存)
如果您只想阅读由多个线程更新的值,则应使用Interlocked.Read()
来保证您拥有counter
的当前值。
答案 1 :(得分:7)
这是因为Increment + WriteLine不是原子的
可能是thread1递增counter
,然后thread2再次递增它,然后两个线程以counter
的相同值到达WriteLine部分。
答案 2 :(得分:1)
我不确定,但我认为这可能是由于变量捕获在lambdas中的工作方式。您是否尝试将其置于单独的函数中,或将变量移到外部并将其声明为静态?