C# Task.WaitAll似乎被窃听了

C# Task.WaitAll似乎被窃听了,c#,task-parallel-library,C#,Task Parallel Library,我正在开发一个模拟工具,可以执行各种数学运算。到目前为止,我还没有必要做并行操作,但现在我真的需要它们。在各种并行方法中,我读到使用任务应该获得最佳性能 我写了这个简单的程序,但我意识到有些地方出了问题 私有静态int任务计数器{get;set;} 私有静态void SimpleTest{taskCounter-;} 静态环[]args { 对于int N=0;N

我正在开发一个模拟工具,可以执行各种数学运算。到目前为止,我还没有必要做并行操作,但现在我真的需要它们。在各种并行方法中,我读到使用任务应该获得最佳性能

我写了这个简单的程序,但我意识到有些地方出了问题

私有静态int任务计数器{get;set;} 私有静态void SimpleTest{taskCounter-;} 静态环[]args { 对于int N=0;N<100;N++ { 任务计数器=0; 列表任务列表=新列表; 对于int i=0;i<100;i++ { taskCounter++; Task Task=Task.Factory.StartNew=>SimpleTest; taskList.Addtask; } Task.WaitAlltaskList.ToArray; Console.WriteLineUnsolved:{0},taskCounter; } } 预期:未解决:所有迭代均为0。 结果:


正如elgonzo在评论中指出的那样。taskCounter++和taskCounter-这两个操作不是原子操作,这意味着它们实际上由几个步骤组成:从内存中读取变量,向其中添加或减去一个变量,然后将结果写回内存

由于多个线程同时执行这些序列,最终结果可能会变得不正确。 假设线程1和线程2同时从内存中读取变量,分别增加各自的副本,然后尝试将结果写回内存。在这种情况下,写入内存的最终值与相继执行这两个序列的情况不同


现代CPU的高速缓存使得并行执行的操作很可能产生错误的结果。如果按照建议使用Interlocked.Increment/Interlocked.decreation,您将得到一致的结果。这两个操作使用硬件支持来保证原子性。

taskCounter++和taskCounter-不是原子操作。以并发方式运行这些操作是不安全的,并且会由于竞争条件而产生意外的、任意的结果。。。对于原子增量和减量操作,请使用Interlocated.Increment/Interlocated.Decrement方法。Interlocated方法要求taskCounter是一个字段;或者将taskCounter保留为属性,但显式实现一个支持字段,该字段随后将由联锁方法修改……为了给出更礼貌的回答,您可能需要对货币进行更多的研究,以真正了解发生了什么。使用访问相似数据的多线程可能会变得非常复杂。然而,正如elgonzo所说,如果你让每个线程隔离,将结果存储到一个并发包中,该并发包设计为线程安全,然后在所有任务完成后合并结果,你可以避免很多复杂性。另一个可能的重复:这种行为既不是bug,也不是任务或WaitAll所特有的。线程也会发生同样的事情。如果关于种族条件的事情对你来说仍然是个谜,Tom Scott不久前制作了一个视频,其中他给出了一个可能的种族条件的例子,当同时递增数字时,这很容易理解:
[...]
Unsolved: 0
Unsolved: 0
Unsolved: 2
Unsolved: -4
Unsolved: -1
Unsolved: -1
Unsolved: 0
Unsolved: 0
Unsolved: 2
Unsolved: -1
[...]