【问题标题】:ReaderWriterLock for array数组的 ReaderWriterLock
【发布时间】:2012-08-14 18:57:41
【问题描述】:

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

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

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

谢谢

【问题讨论】:

  • 数组的底层元素类型是什么?这很重要。
  • k;现在,当您写信给它时,您是否更改了references?或者你正在改变 objects ?即是 arr[12] = newObj; 吗?还是arr[12].Foo = "abc";
  • 视情况而定。两者都应该支持。

标签: c# multithreading locking


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 为了改变单个对象,他们也可以锁定对象本身。只要没有其他东西可能锁定它(最好的保证 - 没有其他东西甚至可以看到它),那么这是安全的。我已经成功地对主集合使用无锁技术(许多线程同时命中)并锁定包含的可变对象(每个对象的争用率很低,我还通过放置失败的操作的细节获得了更多立即获得锁到队列中以供以后处理,因此线程可以继续进行那些没有当前争用的操作。
  • 值得注意的是,虽然你给出的两个例子是安全的,但串联起来却不是(我知道,你知道,但是有人在读这个......)
  • @Jon 确实如此,尽管这样会变得更加棘手,因为您需要知道是什么锁定了该对象。我实际上希望 Monitor 是一个 instance 类,并且您必须锁定一个显式的 Monitor 实例 - 这会使锁定问题大大减少。对于粒度锁定,老实说,我认为我更喜欢您的条带选项。
  • 我自己也想过几次关于监视器的想法,尽管在我们今天不需要它的情况下它会迫使一个人进入像条带一样的状态。当“感兴趣的对象”对我们的代码是私有的并且“感兴趣的对象”很容易定义时,我们可以安全地锁定“感兴趣的对象”,这种情况很多但远未涵盖所有情况。
  • 嘿,现在发生了,我提到的那个“成功”是我做过的最复杂的现实生产代码多线程的一部分,所以现在它可能不是最好的例子我考虑了一下。
【解决方案2】:

首先,您可能不需要任何锁。使用 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(我想不出这种特殊的递归锁定会出错的方法,但为什么不呢?只是避免它而不是证明它是递归锁定是安全的情况之一?)

【讨论】:

  • 我喜欢只使用 8 个锁的想法,但我认为不适合。但也是个好主意。我想我可以在其他地方使用它;)谢谢。
  • 您可以在您的问题中添加更多关于它为什么不适合的问题。理想情况下,我回答的第一部分“您可能不需要任何锁”适合,但您必须真的确定它适合!否则,有多种选择,但在建议之前确实需要更多细节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多