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
MVarimplementation 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
MVaris 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
MVarmight 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
takefollowed by aputrule is only for your “modify” use-case.The problem is that a
modifyfunction that’s described viatake+putis not an atomic operation and this is a problem, but only if any of the actors accessing theMVarare 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
takemust 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
AtomicReferencedue 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).