我正在开发执行各种数学运算的仿真工具。到目前为止,我还不需要进行并行操作,但是现在我确实需要它们。在各种并行方法中,我读到应该使用Tasks获得最佳性能。
我写了这个简单的程序,但是我意识到有些问题。
private static int taskCounter { get; set; }
private static void SimpleTest() { taskCounter--; }
static void Main(string[] args)
{
for (int N = 0; N < 100; N++)
{
taskCounter = 0;
List<Task> taskList = new List<Task>();
for (int i = 0; i < 100; i++)
{
taskCounter++;
Task task = Task.Factory.StartNew(() => SimpleTest());
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine("Unsolved: {0}", taskCounter);
}
}
预期:未解决:所有迭代均为0。 结果:
[...]
Unsolved: 0
Unsolved: 0
Unsolved: 2
Unsolved: -4
Unsolved: -1
Unsolved: -1
Unsolved: 0
Unsolved: 0
Unsolved: 2
Unsolved: -1
[...]
答案 0 :(得分:0)
正如elgonzo在他的评论中指出的。这两个操作taskCounter++
和taskCounter--
不是原子的,这意味着它们实际上由几个步骤组成(从内存中读取变量,向其添加或减去一个变量,然后将结果写回到内存中。
在多个线程同时执行这些序列的情况下,最终结果可能会变得不正确。 假设线程1和线程2同时从内存中读取变量,同时分别递增其各自的副本,然后尝试将结果写回到内存中。在这种情况下,写入内存的最终值与两个序列一个接一个地执行是不一样的。
现代CPU的高速缓存使并行执行的那些操作很可能产生错误的结果。如果按照建议使用Interlocked.Increment / Interlocked.Decrement
,您将获得一致的结果。这两个操作使用硬件支持来保证原子性。