读写一个多线程共享数组

本文关键字:多线程 共享 数组 一个 读写 | 更新日期: 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变量)

如何使以上代码以线程安全的方式并发运行?

读写一个多线程共享数组

中的value变量
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不适合你的需要,这个命名空间有一堆不同类型的集合,都是线程安全的,如字典,堆栈,队列等。