数组的读写锁(ReaderWriterLock)

4

我有一个数组,代表库存,其中有近50个元素(物品:一些自定义对象),我需要为它使用读写锁(好的,我认为一个简单的锁也足够了)。它应支持引用更改和值更改。

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

我肯定可以创建50个读写锁,但我不想这样做;) 有没有办法实现这个目标?(我知道 ConcurrentList/Dictionary 等,但我想要一个数组...)

谢谢


数组的基础元素类型是什么?这很重要。 - Marc Gravell
现在,当你写入它时,你是改变了引用吗?还是改变了对象?即是arr[12] = newObj;?还是arr[12].Foo = "abc"; - Marc Gravell
这要看情况。两者都应该被支持。 - hellow
2个回答

5

如果您要替换数组中的引用,那么这已经是安全的了,因为引用交换本质上是原子操作。因此您可以使用以下代码:

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

并且

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

至少在避免引用错误方面,对象可以完全安全地使用。因此,一种实用的选择是使对象不可变,并仅使用上述方法。如果需要更改值,请取一个本地副本,基于它创建一个新版本,然后交换它们。如果丢失更新是一个问题,则可以非常成功地使用Interlocked.CompareExchange和重新应用循环(即,您一直重新应用更改直到“胜利”)。这样就避免了任何锁定的需要。
然而,如果您正在改变单个对象,则游戏规则会发生改变。您可以使对象内部线程安全,但这通常并不美观。您可以为所有对象设置单个锁定。但如果您想要粒度锁定,则需要多个锁。
我的建议是:使对象不可变,并只使用原子引用交换技巧。

对于变异个别对象,也可以锁定对象本身。只要没有其他东西可能锁定它(最好的保证是 - 没有其他东西甚至能看到它),那么这是安全的。我成功地使用了无锁技术来处理主集合(许多线程同时访问)和包含可变对象的锁(每个对象的争用较低,我还通过将无法立即获取锁的操作的详细信息放入队列中以供以后处理,因此线程可以处理这些操作而没有当前的争用)。 - Jon Hanna
值得注意的是,尽管你提供的这两个例子都是安全的,但是它们一起使用就不安全了(我知道,你知道,但是读到这篇文章的人可能不知道)。 - Jon Hanna
@Jon 的确如此,但是接下来会变得更加棘手,因为您需要知道锁定该对象的是什么。我其实希望 Monitor 是一个实例类,并且您需要锁定一个明确的 Monitor 实例——这将使锁定变得不那么棘手。对于粒度锁定,老实说,我更喜欢您的分段选项。 - Marc Gravell
我自己也曾考虑过监视器的问题,但这将迫使我们在不需要条带化的情况下进行条带化。当“感兴趣的对象”是私有于我们的代码并且“感兴趣的对象”容易定义时,我们可以安全地锁定它,这种情况经常出现但远不能覆盖每种情况。 - Jon Hanna
哈,现在发现我提到的"成功"是我所做过的最复杂的实际生产代码多线程之一,所以现在想想它可能不是最好的例子。 - Jon Hanna

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(我想不出这种特定递归锁定会出什么问题,但为什么不避免它,而不是必须证明它是安全的情况之一?)


我喜欢只使用8个锁的想法,但在我的情况下不适用。但这也是个好主意。我想我可以在别处使用它 ;) 谢谢。 - hellow
你可以在问题中进一步说明为什么它不适合。理想情况下,我的回答的第一部分“你可能不需要任何锁”是适用的,但你必须非常确定它是否适用!否则,有各种选项,但在建议之前需要更多细节。 - Jon Hanna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接