And one more thing, you mentioned the Synchronized
implementation at:
N.B. I happen to believe this is harmful because:
On point number 2, this is important because it shows that Ref
isn’t very adequate to build Synchronized
.
On point number 1 … this extends to MVar
usage too. If you have blocking behaviour in a way that prevents threads from making progress, such solutions don’t scale and it doesn’t matter how you build it (MVar
or IORef
or whatever), it’s still going to be a bottleneck.
--
Alexandru Nedelcu
https://alexn.org
On 2 Jan 2019, at 2:44, Alexandru Nedelcu wrote:
Hello,
I’m the author of the
MVar
implementation in Cats-Effect.Recently I had an interesting discussion on MVars with cats-effect library
designers. Cats-effect brings MVar synchronization primitive along with
other IO stuff to the Scala programming language. I tried to persuade them
to include some Control.Concurrent.MVar’s functions to the library but has
failed completely. Moreover, now I think that MVar is a poor choice for
basic synchronization primitive.I believe
MVar
is a superb choice for synchronisation, because it behaves like a blocking queue, which in computer science is a pretty fundamental tool.It is true that in Cats-Effect an
MVar
might not be a primitive, but as I explained in that thread, on top of the JVM the real primitives are the low level building blocks likeAtomicReference
.1. It’s complex. Each MVar has 2 state transitions, each may block.
Blocking is a feature. If you have to build that logic via
Ref
(IORef
), you’d have to model the state machine by yourself and that’s complexity being pushed to the user.2. It does not play well in presence of asynchronous exceptions. More
specifically, `take` and `put` operations should be balanced (each `take`
must be followed by `put`)The
take
followed by aput
rule is only for your “modify” use-case.The problem is that a
modify
function that’s described viatake
+put
is not an atomic operation and this is a problem, but only if any of the actors accessing theMVar
are doing so in a different order.This isn’t a problem for other use-cases tough.
this force programmer to mask asynchronous
exceptions during the MVar acquisition and since `take` function may block,
this will delay task cancelation.I don’t have much experience with Haskell’s async exceptions, but if you mean that
take
must be executed asuncancelable
, viabracket
, then this is a problem that we can fix in Cats-Effect 2.x, as mentioned in that thread.What could be the sensible alternative? Guy from the cats-effect suggested
me IVar + atomic reference (IORef). This pattern separates concern of
blocking (synchronization) from the atomic mutation. So everything can be
represented as atomic reference with IVar inside. Just look at this
beautiful mutex implementation
https://github.com/ovotech/fs2-kafka/blob/master/src/main/scala/fs2/kafka/internal/Synchronized.scalaAs I said above, most things on top of the JVM can be implemented via an
AtomicReference
due to its memory model and this is no exception.But it’s not elegant, or simple ;-)
(By ”beautiful” I mean approach itself of course, but not the Scala’s
syntax. Scala is one of most ugliest girls after C++ I was forced to date
with by my employer for money. Thankfully he didn’t force me to do the same
things with her grandma Java).