Scala 中 monad 中的可重入锁
Reentrant locks within monads in Scala
我的一位同事说了以下关于在一些 Scala 代码中使用 Java ReentrantReadWriteLock
的内容:
Acquiring the lock here is risky. It's "reentrant", but that internally depends on the thread context. F
may run different stages of the same computation in different threads. You can easily cause a deadlock.
F
这里指的是一些有效的monad。
基本上我想做的是在同一个 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
的writeLock
。我以这种方式编写代码有一些原因,所以我不想深入研究。我只想了解为什么(至少根据我的同事的说法),这可能会破坏内容。
另外,我想知道 Scala 中是否有一些替代方案可以允许类似的事情而不会出现死锁风险。
IIUC 你的问题:
您希望与资源的每次交互 lock.lock
和 lock.unlock
操作都发生在同一线程中。
1) 由于您在这里使用了任意效果 F
,因此根本无法保证。
可以编写 F
的实现,在新线程中执行每个操作。
2) 即使我们假设 F
是 IO
,那么 doSomething
的 body 也有人可以做到 IO.shift
。因此,包括 unlock
在内的下一个操作将在另一个线程中发生。 doSomething
的当前签名可能是不可能的,但你明白了。
Also, I'd like to know if there is some alternative in Scala that would allow something like this without the risk for deadlocks.
你可以看看scalaz zio STM
。
我的一位同事说了以下关于在一些 Scala 代码中使用 Java ReentrantReadWriteLock
的内容:
Acquiring the lock here is risky. It's "reentrant", but that internally depends on the thread context.
F
may run different stages of the same computation in different threads. You can easily cause a deadlock.
F
这里指的是一些有效的monad。
基本上我想做的是在同一个 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
的writeLock
。我以这种方式编写代码有一些原因,所以我不想深入研究。我只想了解为什么(至少根据我的同事的说法),这可能会破坏内容。
另外,我想知道 Scala 中是否有一些替代方案可以允许类似的事情而不会出现死锁风险。
IIUC 你的问题:
您希望与资源的每次交互 lock.lock
和 lock.unlock
操作都发生在同一线程中。
1) 由于您在这里使用了任意效果 F
,因此根本无法保证。
可以编写 F
的实现,在新线程中执行每个操作。
2) 即使我们假设 F
是 IO
,那么 doSomething
的 body 也有人可以做到 IO.shift
。因此,包括 unlock
在内的下一个操作将在另一个线程中发生。 doSomething
的当前签名可能是不可能的,但你明白了。
Also, I'd like to know if there is some alternative in Scala that would allow something like this without the risk for deadlocks.
你可以看看scalaz zio STM
。