读写一个多线程共享数组
本文关键字:多线程 共享 数组 一个 读写 | 更新日期: 2023-09-27 18:12:58
我有以下代码:
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
变量)
如何使以上代码以线程安全的方式并发运行?
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个数组项,并且(困难的部分)以一种不会发生死锁的方式锁定它们。而且,即使您提出了一个解决方案,所有锁的开销实际上也可能使代码比非并发地执行更慢。
您应该考虑在Systems.Collections.Concurrent
命名空间中使用BlockingCollection。
如果BlockingCollection不适合你的需要,这个命名空间有一堆不同类型的集合,都是线程安全的,如字典,堆栈,队列等。