TReentrantLock

TReentrantLock(可重入锁)允许效果性地、安全地并发访问某些可变状态,从而允多个纤程读取其中的状态(因为并发读取是安全的),但是只有一个纤程可以修改状态(以防止数据损坏)。此外,TReentrantLock 是使用 STM 实现的,读写操作都可以当作事务进行提交,因此可以将其用作纯 ZIO effect 解决方案的基石,并在内部允许以简单且可组合的方式锁定多个状态(感谢 STM)。

TReentrantLock 是可重入的读/写锁。可重入锁是一个纤程可以多次取得而不会对其自身造成阻塞的锁。在难以跟踪您是否已经获得锁的情况下,此功能很有用。如果锁是不可重入的,则在你获得该锁后再次去获得它时会被阻塞,从而实际上导致了死锁。

语义

该锁对读取方和写入方都提供了重新获得读取或写入锁的重入保证。在释放由写纤程持有的所有写锁之前,不允许读纤程进入。除非没有其他纤程持有写入锁,或者想要写入的纤程已经持有读取锁且没有其他纤程也持有读取锁,否则不允许写纤程进入。

此锁还允许从读锁升级到写锁(自动),以及从写锁降级到读锁(前提是您从读锁升级到写锁是自动的)。

创建一个可重入锁

import zio.stm._

val reentrantLock = TReentrantLock.make

获取一个读锁

import zio.stm._

val program =
  (for {
    lock <- TReentrantLock.make
    _    <- lock.acquireRead
    rst  <- lock.readLocked  // lock is read-locked once transaction completes
    wst  <- lock.writeLocked // lock is not write-locked
  } yield rst && !wst).commit

获取一个写锁

import zio._
import zio.stm._

val writeLockProgram: UIO[Boolean] =
  (for {
    lock <- TReentrantLock.make
    _    <- lock.acquireWrite
    wst  <- lock.writeLocked // lock is write-locked once transaction completes
    rst  <- lock.readLocked  // lock is not read-locked
  } yield !rst && wst).commit

多个纤程可同时持有读锁

import zio._
import zio.stm._

val multipleReadLocksProgram: UIO[(Int, Int)] = for {
  lock          <- TReentrantLock.make.commit
  fiber0        <- lock.acquireRead.commit.fork // fiber0 acquires a read-lock
  currentState1 <- fiber0.join                  // 1 read lock held
  fiber1        <- lock.acquireRead.commit.fork // fiber1 acquires a read-lock
  currentState2 <- fiber1.join                  // 2 read locks held 
} yield (currentState1, currentState2)

锁升级和降级

如果您的纤程已经持有读锁,则可以将其升级为写锁,前提是(除了当前纤程外)没有其他读取纤程持有该锁。

import zio._
import zio.stm._

val upgradeDowngradeProgram: UIO[(Boolean, Boolean, Boolean, Boolean)] = for {
  lock               <- TReentrantLock.make.commit
  _                  <- lock.acquireRead.commit
  _                  <- lock.acquireWrite.commit  // upgrade
  isWriteLocked      <- lock.writeLocked.commit   // now write-locked
  isReadLocked       <- lock.readLocked.commit    // and read-locked
  _                  <- lock.releaseWrite.commit  // downgrade
  isWriteLockedAfter <- lock.writeLocked.commit   // no longer write-locked
  isReadLockedAfter  <- lock.readLocked.commit    // still read-locked
} yield (isWriteLocked, isReadLocked, isWriteLockedAfter, isReadLockedAfter)

在有争议的情况下获取写锁定

只有满足以下条件之一,才能立即获取写锁定:

  1. 没有其他的(写入)锁持有人。
  2. 当前的纤程已经持有读取锁,且没有其他方持有读取锁。

如果以上两种情况都不满足,则尝试获取写锁定将在语义上阻塞当前纤程。这是一个示例,说明仅当所有其他读取器(尝试获取写锁的纤程除外)释放对(读或写)锁的锁定后,该纤程才能获得写锁。

import zio._
import zio.clock._
import zio.console._
import zio.stm._
import zio.duration._

val writeLockDemoProgram: URIO[Console with Clock, Unit] = for {
  l  <- TReentrantLock.make.commit
  _  <- putStrLn("Beginning test")
  f1 <- (l.acquireRead.commit *> ZIO.sleep(5.seconds) *> l.releaseRead.commit).fork
  f2 <- (l.acquireRead.commit *> putStrLn("read-lock") *> l.acquireWrite.commit *> putStrLn("I have upgraded!")).fork
  _  <- (f1 zip f2).join
} yield ()

此例中纤程 f1 获得读取锁定,并在释放它之前休眠 5 秒钟。同时纤程 f2 尝试获取读锁定,并立即尝试获取写锁定。但是,f2 必须在语义上阻塞大约5 秒钟才能获得写锁,因为 f1 到时才将释放其对锁的保留,然后 f2 才能获取写锁定。

更安全的方法(readLock and writeLock)

对于一些简单的使用案例,应避免使用 acquireReadacquireWritereleaseReadreleaseWrite ,而应该使用诸如 readLockwriteLock 之类的方法。借助 Managed 构造,readLockwriteLock 可以自动获取并释放锁。下面的程序是一个比上面的例子更安全的版本,它确保一旦通过可重入锁完成操作,我们就不会占用(会释放)任何资源。

import zio._
import zio.clock._
import zio.console._
import zio.stm._
import zio.duration._

val saferProgram: URIO[Console with Clock, Unit] = for {
  lock <- TReentrantLock.make.commit
  f1   <- lock.readLock.use_(ZIO.sleep(5.seconds) *> putStrLn("Powering down")).fork
  f2   <- lock.readLock.use_(lock.writeLock.use_(putStrLn("Huzzah, writes are mine"))).fork
  _    <- (f1 zip f2).join
} yield ()
Leave a Reply
Your email address will not be published.
*
*

BACK TO TOP