如果我使用
ReaderWriterLockSlim
来获取读/写锁,我是否需要创建变量 volatile
或使用 Interlocked.Increment
?
例如,下面
Add
方法中的代码可以正常工作吗?还是需要增强?
public class AppendableList<T> { // semi-immutable; supports appending only
private T[] data = new T[16];
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
public int Count { get; private set; }
public T this[int index] {
get {
rwLock.EnterReadLock();
try { return data[index]; } finally { rwLock.ExitReadLock(); }
}
}
public void Add(T item) {
rwLock.EnterUpgradeableReadLock();
try {
if (Count == data.Length)
reAllocateArray(); // upgrades to write lock
data[Count++] = item; // do I need to use Interlocked here?
} finally { rwLock.ExitUpgradeableReadLock(); }
}
}
编辑:我正在尝试编写一个轻量级、快速且简单的列表,该列表允许多个线程同时访问其数据(类似于生产者-消费者缓冲区)。我已经编辑了上面的代码,删除了我之前使用的简化,所以现在问题应该更清楚了。在我看来,这段代码是线程安全的,但我不确定退出可升级锁后是否所有线程都会立即看到
Count
的更新值。
编辑2:此处的“写入”锁用于指示写入数组引用,而不是数组元素。我假设这已经足够了(因为数据本身是不可变的)。我想在增加
Count
时我需要使用Interlocked。这是真的吗?
我完全期望写锁能够充当内存屏障(特别是在写锁内),但我无法立即证明这一点。
是否需要
ReaderWriterLockSlim
的复杂性取决于上下文; Interlocked
、volatile
、lock
或 [MethodImpl]
可能会更简单地完成这项工作。如果你有很多读者而很少作者,你主要需要ReaderWriterLock[Slim]
。
但是,
get
当前不受锁保护;如果您需要写锁跨越多个操作(读者看不到中间值),您将需要我们显式的属性实现并自己取出读锁。
顺便说一句,人们可能更熟悉
Count++
的用法。
您还应该使用
try
/finally
来确保在异常时释放锁定。
为了避免先读后写锁的问题,也许:
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private int count;
public int Count {
get {
rwLock.EnterReadLock();
int tmp = count;
rwLock.ExitReadLock();
return tmp;
}
}
public void Add(object x) {
rwLock.EnterWriteLock();
try {
// do some processing
count++;
} finally {
rwLock.ExitWriteLock();
}
}
更新了您的编辑;
看起来很坚固。我建议使用
List<T>
(而不是 T[]
数组),因为它将在内部完成所有加倍等操作,从而节省大量代码。由于一次只有一个线程可以更新 Count
,因此不需要 Interlocked
,并且此属性节省了读取 Count
时对锁的需要,只要调用者可以获取旧的即可Count
当另一个线程正在添加行(而不是被阻塞)时。
是的确实如此,为了深入了解各种内存屏障情况,请查看该文档,如果您愿意,您也可以找到非围栏锁。
请,不要使用挥发性物质,现在它的效果越来越差!!
所有标准 Windows 锁定 机制(自旋锁、互斥锁、 内核事件和资源管理 由执行资源包提供) 防止处理器重新排序 通过在其中插入内存屏障 可执行代码中需要。
内存屏障是一个处理器 保留的指令 读和/或写的顺序 从任何角度进行操作 其他处理器。记忆障碍 包括处理器指令 获取、释放和栅栏语义。 这些语义描述了顺序 操作的结果变成 可见。
- 获取语义意味着操作的结果是可见的 在任何操作结果之前 在代码中出现在它后面。
- 释放语义意味着操作结果可见 任何操作结果之后 在代码中出现在它之前。
- Fence 语义结合了获取和释放语义。结果 带有栅栏语义的操作是 在任何操作之前可见 在代码中出现在它之后 任何操作之后 出现在它之前。