使用 ReaderWriterLockSlim 会导致内存障碍吗?

问题描述 投票:0回答:2

如果我使用

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。这是真的吗?

.net multithreading locking
2个回答
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
投票

是的确实如此,为了深入了解各种内存屏障情况,请查看该文档,如果您愿意,您也可以找到非围栏锁。

请,不要使用挥发性物质,现在它的效果越来越差!!

所有标准 Windows 锁定 机制(自旋锁、互斥锁、 内核事件和资源管理 由执行资源包提供) 防止处理器重新排序 通过在其中插入内存屏障 可执行代码中需要。

内存屏障是一个处理器 保留的指令 读和/或写的顺序 从任何角度进行操作 其他处理器。记忆障碍 包括处理器指令 获取、释放和栅栏语义。 这些语义描述了顺序 操作的结果变成 可见。

  • 获取语义意味着操作的结果是可见的 在任何操作结果之前 在代码中出现在它后面。
  • 释放语义意味着操作结果可见 任何操作结果之后 在代码中出现在它之前。
  • Fence 语义结合了获取和释放语义。结果 带有栅栏语义的操作是 在任何操作之前可见 在代码中出现在它之后 任何操作之后 出现在它之前。
© www.soinside.com 2019 - 2024. All rights reserved.