【发布时间】:2019-05-31 16:09:31
【问题描述】:
我正在开发一个执行各种数学运算的模拟工具。到目前为止,我还没有需要做并行操作,但现在我真的需要它们。在我读过的各种并行方法中,应该使用任务来实现最佳性能。
我写了这个简单的程序,但我意识到出了点问题。
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
[...]
【问题讨论】:
-
taskCounter++和taskCounter--不是原子操作。以并发方式运行这些操作是不安全的,并且会由于竞争条件而产生意外的任意结果...使用Interlocked.Increment/Interlocked.Decrement方法进行原子递增和递减操作(互锁方法需要taskCounter一个字段;或保留taskCounter一个属性,但显式实现一个支持字段,然后将由互锁方法修改...) -
要给出更礼貌的答案,您可能需要对货币进行更多研究才能真正了解发生了什么。使用访问相似数据的多个线程会变得非常复杂。但是,正如 elgonzo 所说,如果您让每个线程隔离,将结果存储到类似并发包(设计为线程安全的)中,然后在所有任务完成后合并结果,您可以避免很多复杂性。
-
这种行为既不是错误也不是
Tasks 或WaitAll()独有的。Threads 也会发生同样的事情。 -
如果关于比赛条件的事情对你来说仍然是个谜,汤姆斯科特不久前制作了一个视频,他(除其他外)给出了一个同时增加数字时可能的比赛条件的例子,这很简单了解:youtube.com/watch?v=RY_2gElt3SA