4

我有一个数组,它代表一个库存,有近 50 个元素(项目:一些服装对象),我需要一个 readerwritelock (好吧,我认为一个简单的锁也足够了)。它应该支持引用更改和值更改。

由于读取和写入数组的不同位置是线程安全的(证明),我想确保对同一数组位置的多个读/写操作也是线程安全的。

我当然可以创建 50 个 readerwriterlocks,但我不想要那个 ;) 有没有办法存档这个?(我知道 ConcurrentList/Dictionary/etc。但我想要一个数组......)

谢谢

4

2 回答 2

5

如果您要替换数组中的引用,那么这已经是安全的,因为引用交换本质上是原子的。所以你可以使用:

var val = arr[index];
// now work with val

var newVal = ...
arr[index] = newVal;

完全安全,至少在避免撕裂参考方面。因此,一种实用的选择是使对象不可变,并仅采用上述方法。如果您需要更改该值,请获取本地副本,根据该副本制作新版本,然后交换它们。如果丢失的更新是一个问题,那么Interlocked.CompareExchange可以非常成功地使用重新应用循环(即您不断重新应用您的更改,直到您“获胜”)。这避免了任何锁定的需要。

但是,如果您正在改变单个对象,那么游戏就会改变。您可以使对象在内部成为线程安全的,但这通常并不美观。您可以为所有对象设置一个锁。但是,如果您想要粒度锁定,那么您将需要多个锁。

我的建议:使对象不可变,只需使用原子引用交换技巧。

于 2012-08-14T10:57:41.307 回答
3

首先,您可能不需要任何锁。使用 CPU 以原子方式处理每次读取和写入的类型的数组进行读取和写入本身是线程安全的(但您可能需要放入内存屏障以避免过时的读取)。

也就是说,就像x = 34整数是线程安全的,但x++不是,如果您的写入依赖于当前值(因此是读取和写入),那么这不是线程安全的。

如果您确实需要锁,但不想要多达 50 个,则可以进行条带化。首先设置你的条带锁(我将使用简单的锁而不是ReaderWriterSlim较小的示例代码,同样的原则适用):

var lockArray = new object[8];
for(var i =0; i != lockArray.Length; ++i)
  lockArray[i] = new object();

然后当你去使用它时:

lock(lockArray[idx % 8])
{
  //operate on item idx of your array here
}

这是一个锁的简单性和大小与每个元素的一个锁的内存使用之间的平衡。

如果一个元素上的操作依赖于另一个元素的操作,如果您需要调整数组的大小,或者您需要拥有多个锁的任何其他情况,则会出现很大的困难。总是以相同的顺序获取锁可以避免很多死锁情况(因此没有其他需要多个锁的线程会尝试获取您已经拥有的锁而持有您需要的锁),但是您需要非常小心这些案例。

您还想确保,如果您正在处理索引 3 和索引 11,则避免两次锁定对象 3(我想不出这种特定的递归锁定会出错的方法,但为什么不避免它呢?而不是必须证明它是递归锁定是安全的情况之一?)

于 2012-08-14T10:57:14.867 回答