我的一位同事针对在某些Scala代码中使用Java ReentrantReadWriteLock
作了如下说明:
在这里获取锁是有风险的。它是“可重入的”,但是在内部取决于线程上下文。
F
可以在不同线程中运行同一计算的不同阶段。您很容易造成死锁。
F
是指一些有效的单子。
基本上,我想做的是在同一monad中两次获得相同的可重入锁。
有人可以弄清楚为什么这可能是个问题吗?
该代码分为两个文件。最外面的一个:
val lock: Resource[F, Unit] = for {
// some other resource
_ <- store.writeLock
} yield ()
lock.use { _ =>
for {
// stuff
_ <- EitherT(store.doSomething())
// other stuff
} yield ()
}
然后,在store
中:
import java.util.concurrent.locks.{Lock, ReentrantReadWriteLock}
import cats.effect.{Resource, Sync}
private def lockAsResource[F[_]](lock: Lock)(implicit F: Sync[F]): Resource[F, Unit] =
Resource.make {
F.delay(lock.lock())
} { _ =>
F.delay(lock.unlock())
}
private val lock = new ReentrantReadWriteLock
val writeLock: Resource[F, Unit] = lockAsResource(lock.writeLock())
def doSomething(): F[Either[Throwable, Unit]] = writeLock.use { _ =>
// etc etc
}
两段代码中的writeLock
是相同的,它是一个cats.effect.Resource[F, Unit]
,其中包裹着ReentrantReadWriteLock
的{{1}}。我以这种方式编写代码有一些原因,所以我不想对此进行深入研究。我只想了解为什么(至少根据我的同事),这可能会破坏东西。
此外,我想知道Scala中是否有其他选择可以允许这样的事情而没有死锁的风险。
答案 0 :(得分:2)
IIUC您的问题:
您希望与资源lock.lock
和lock.unlock
的每次交互都在同一线程中发生。
1)由于您在此处使用任意效果F
,因此根本无法保证。
可以编写F
的实现,以在新线程中执行每个动作。
2)即使我们假设F
是IO
,那么doSomething
的主体还是可以IO.shift
做的。因此,包括unlock
在内的下一个动作将在另一个线程中发生。当前的doSomething
签名可能无法实现,但是您可以理解。
此外,我想知道Scala中是否有其他选择可以允许这样的事情而没有死锁的风险。
您可以看看scalaz zio STM
。