我有以下代码:
const int MAX = 101;
const int MIN_COMBINATORIC = 1000000;
int[,] pascalTriangle = new int[MAX, MAX];
Parallel.For(0, MAX, i =>
{
pascalTriangle[i, 0] = 1;
pascalTriangle[i, i] = 1;
});
int counter = 0;
Parallel.For(0, MAX, x => Parallel.For(1, x, y =>
{
int value = pascalTriangle[x - 1, y] + pascalTriangle[x - 1, y - 1];
Interlocked.Exchange(ref pascalTriangle[x, y], value < MIN_COMBINATORIC ? value : MIN_COMBINATORIC);
if(value > MIN_COMBINATORIC)
Interlocked.Increment(ref counter);
}));
Console.WriteLine("Result: {0}", counter);
问题在于它有时打印出正确答案(Result: 4075
),但有时它会打印一个随机(和错误)的答案,例如:
Result: 2076
Result: 1771
Result: 0
我猜这与我在多个线程之间读取和编写共享数组这一事实有关。
正如您所看到的,我尝试添加Interlocked.Exhange()
用于线程安全的写操作,但我找不到类似的读取方法(有一个Interlocked.Read()
但它只能读取long
个变量)
如何以线程安全的方式同时运行上述代码?
答案 0 :(得分:3)
中的值变量
int value = pascalTriangle[x - 1, y] + pascalTriangle[x - 1, y - 1];
依赖于数组中的两个项目。由于并发性,这些值可以在添加这两个项目之间发生变化,将它们存储到value
中,并将该值传递给Interlocked.Exchange
。这意味着,当您在value
中存储pascalTriangle[x, y]
时,pascalTriangle[x - 1, y]
和/或pascalTriangle[x - 1, y - 1]
中的值可能已更改。
我实际上无法想到一个简单的并发解决方案。显而易见的解决方案是不要同时执行此操作。如果这是你想要使用的实际代码,那么同时运行它并没有什么好处,因为它只循环101 * 101 = 10,201次,这可以很快完成(初始化三角形的第一个并行代码只是循环101)次,所以也可以是单线程的)。如果要创建多个这样的三角形,那么您可能会同时创建三角形(换句话说,创建三角形的方法是单线程的,但调用者在不同的线程上多次调用此方法)。
如果你真的想要一个并发解决方案,你将需要弄清楚如何实现一个锁定机制,可以锁定你正在访问的3个数组项,并且(困难的部分)锁定它们,使得不会发生死锁。即使你想出了一个解决方案,所有锁的开销实际上可能会使代码慢于非并发执行。
答案 1 :(得分:0)
您应该考虑在Systems.Collections.Concurrent
命名空间中使用BlockingCollection。
如果BlockingCollection不能满足您的需求,则此命名空间有许多不同类型的集合,这些集合都是线程安全的,如字典,堆栈,队列等。