我想知道使用可升级读锁的优点是什么,而不是执行这些步骤:
- 读取锁定
- 检查条件以确定是否需要进行写锁定
- 释放读锁定
- 采取写锁定
- 执行更新
- 释放写锁定
醇>
执行上述步骤的一个明显缺点是,采用可升级的读锁定,步骤3和4之间有一个时间窗口,其中另一个线程可以占用写锁定。
除了这个优势之外,您还可以通过我上面提到的步骤获得可升级读锁的其他优势吗?
答案 0 :(得分:7)
让我们考虑使用不具有单独“可升级读卡器”的读写器锁的不同方式。
使用您的模式,当您指出时,在步骤3和4之间存在竞争,其中另一个线程可以采用写入程序锁定。更重要的是,在3到4之间有一个步骤,其中一个线程可以使写入器锁定并改变我们在步骤2中观察到的状态。
因此,我们有四种选择,具体取决于这种情况如何发生:
我们坚持你的方法,因为这实际上是不可能的(例如,在我们的应用程序中,给定的状态转换是单向的,所以一旦观察到它是永久性的)。在这种情况下,虽然我们很可能已经重新设计,以便根本不需要锁。 (单向转换适用于无锁技术)。
我们只是首先使用编写器锁,因为我们在步骤2中观察到的状态很可能会发生变化,并且用读卡器锁来检查它是浪费时间。
我们将您的步骤更改为:
我们改为:
不难看出为什么4对某些人来说更具吸引力,尽管只是稍微难以看出它如何使死锁易于创建。可悲的是,稍微用力就足以让很多人看到优势而不会看到劣势。
对于没有发现它的人,如果两个线程有一个读锁定,其中一个线程升级到写锁定,它必须等待另一个线程释放读锁定。但是,如果第二个线程在没有释放读锁的情况下升级到写锁,那么它将在第一个线程上永远等待,这将永远等待它。
如上所述,我认为哪种方法最好取决于国家在此期间改变的可能性(或者我们想要对它做出多快反应)。即使是非发布升级的最后一种方法也可以在可行的代码中占有一席之地,只要有一个线程可以尝试升级其锁而不释放。
除了最后一个选项有效的特殊情况之外,其他选项之间的差异都与性能有关,哪个性能最高,主要取决于重新检查状态的成本以及写入中止的可能性在此期间改变。
但是,请注意,所有这些都涉及获取写入锁,因此所有这些都会阻止所有读取线程,即使写入确实已中止。
可升级的读锁为我们提供了一个中间地带,因为它们阻止了写锁和其他可升级的读锁,但它们不会阻止读锁。它们可能更好,但不像读锁一样可以升级为尚未提交写入的写锁。*在决定不升级的情况下,对读取线程的影响为零。
这意味着如果线程稍微有可能决定不改变状态,那么读取线程不会受到影响,并且性能的提高肯定可以证明它的使用。
*就此而言,“读者 - 作家”有点用词不当,我们可以例如使用ReaderWriterLockSlim
保护一个int或对象的数组,使用读锁来原子地读取和写入单个项目,并使用写锁定进行需要读取整个数组的操作,而不会更改其中的部分内容。读取。在这种情况下,它是一个读取操作,而不是需要独占锁,而共享锁的写操作则很好。
答案 1 :(得分:0)
它还可以防止可能发生的死锁,因为不同的线程同时运行并且彼此等待释放锁。