如果我使用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。这是真的吗?
答案 0 :(得分:3)
我完全期望写锁定作为内存屏障(特别是在写锁定中),但我无法证明它是不可能的。
是否需要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
而另一个线程正在添加行(而不是被阻止)。
答案 1 :(得分:1)
Yes对于各种内存屏障情况的深入概述,检查该文档,如果需要,还可以找到非fence'd锁。
并请,请勿使用它们,这些日子不会太有效!
所有标准Windows锁定 机制(自旋锁,互斥锁, 内核事件和管理的资源 由执行资源包) 防止处理器重新排序 通过插入内存屏障 可执行代码中需要。
内存屏障是处理器 保留的指令 读和/或写的排序 从任何角度看操作 其他处理器。记忆障碍 包含处理器指令 获取,释放和围栅语义。 这些语义描述了顺序 哪个操作的结果变成了 可见。
- 获取语义意味着操作的结果是可见的 在任何操作的结果之前 它出现在代码之后。
- 释放语义意味着操作的结果是可见的 任何操作结果后 在代码中出现在它之前。
- Fence语义结合了获取和释放语义。结果一个 使用fence语义的操作是 在任何操作之前都可见 它出现在代码之后 在任何操作之后 出现在它面前。