可升级的readlock的优势?

nmd*_*mdr 5 c# multithreading locking

我想知道使用可升级读锁的优点是什么,而不是执行以下步骤:

  1. 读取锁定
  2. 检查条件以查看是否需要写入锁定
  3. 释放读锁定
  4. 采取写锁定
  5. 执行更新
  6. 释放写锁定

与采用可升级的读锁相反,执行上述步骤的一个明显缺点是,在步骤3和4之间存在时间窗口,其中另一个线程可以占用写锁定.

除了这个优势之外,您还发现了其他优势,可以通过上面提到的步骤获得可升级的读锁定功能吗?

Jon*_*nna 7

让我们考虑一下可以使用没有单独的"可升级读卡器"的读写器锁的不同方式.

使用您的模式,当您指出时,在步骤3和4之间存在竞争,其中另一个线程可以使用写入程序锁定.更重要的是,在3和4之间有一个步骤,其中一个线程可以获取写入器锁定并更改我们在步骤2中观察到的状态.

因此,我们有四种选择,具体取决于它是如何发生的:

  1. 我们坚持你的方法,因为这实际上是不可能的(例如,在我们的应用中,给定的状态转换是单向的,所以一旦观察到它是永久的).在这种情况下,虽然我们很可能已经重新设计,以便根本不需要锁.(单向转换适用于无锁技术).

  2. 我们只是首先考虑编写器锁,因为我们在步骤2中观察到的状态很可能会发生变化,而且用读卡器锁来检查它是浪费时间.

  3. 我们将您的步骤更改为:

    1. 读取锁定
    2. 检查条件以查看是否需要写入锁定
    3. 释放读锁定
    4. 采取写锁定
    5. 如果发生变化,请重新检查条件.
    6. 执行更新
    7. 释放写锁定
  4. 我们改为:

    1. 对支持递归的锁进行读锁定.
    2. 检查我们是否需要写入锁定.
    3. 写入锁定(不释放读取).
    4. 执行更新.
    5. 释放写锁定.
    6. 释放读锁定.

不难看出为什么4对某些人来说更具吸引力,尽管只看到它如何使死锁易于创建只是稍微困难一些.可悲的是,稍微用力就足以让很多人看到优势而不会看到劣势.

对于没有发现它的人,如果两个线程具有读锁定并且其中一个线程升级到写锁定,则必须等待另一个线程释放读锁定.但是,如果第二个线程在没有释放读锁的情况下升级到写锁,那么它将在第一个线程上永远等待,第一个线程将永远等待它.


如上所述,哪种方法最好取决于国家在此期间改变的可能性(或者我们想要对它做出多快反应,我想).即使是非发布升级的最后一种方法也可以在可行的代码中占有一席之地,只要有一个线程可以尝试升级其锁而不释放.

除了最后一个选项有效的特殊情况之外,其他选项之间的差异都与性能有关,哪个性能最高,主要取决于重新检查状态的成本以及由于更改而导致写入中止的可能性同时.

但是,请注意,所有这些都涉及获取编写器锁,因此所有这些都具有阻止所有读取线程的效果,即使写入确实已中止.

可升级的读锁为我们提供了一个中间地带,因为它们阻止了写锁和其他可升级的读锁,但它们不会阻止读锁.它们可能更好,虽然不是可以升级为尚未提交写入的写锁的读锁.*在决定不升级的情况下,对读取线程的影响为零.

这意味着如果线程甚至可能稍微决定不改变状态,那么读取线程不会受到影响,并且性能的提高肯定可以证明它的使用是正确的.

*就此而言,"读写器"有点用词不当,我们可以用例如用a来保护一个int或对象的数组ReaderWriterLockSlim,使用读锁来原子地读取和写入单个项目并使用写锁定需要读取整个数组而不会在读取时更改部分的操作.在这种情况下,它是一个读取操作,而不是需要独占锁定,而写入操作与共享锁定一样好.