MVar and IVar things are from dataflow programming, I believe, from Id90 programming language (read about it, it's fascinating). They were ued there with greate success (linear scaling on CM-5, no less; they had to invent throttling to tame huge parallelism available). MVar were used for synchronization and IVars to provide a kind of call-by-need in a lenient language. Mind that Id90 was not as sofisticated as Haskell today and these things were used as a coordination tool between huge number of small threads of execution, with no I/O and all threads must work to completion.

Thus, they are, as usually happens, not general purpose yet very useful. I consider them "baby-STM".

But what it takes to consider them harmful is beyond me.

чт, 20 дек. 2018 г. в 00:02, Станислав Черничкин <schernichkin@gmail.com>:

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. Your may find discussion here https://github.com/typelevel/cats-effect/issues/451 and event try to advocate, tl;dr. Anyway, what is so wrong with MVar?

1.       It’s complex. Each MVar has 2 state transitions, each may block.

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`) this force programmer to mask asynchronous exceptions during the MVar acquisition and since `take` function may block, this will delay task cancelation. Well, you may argue what the `takeMVar` function is actually interruptible, but I’m going to show an easier approach which renders interpretability magic unnecessary.

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.scala (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).

For people who don’t read Scala, the approach is fairly simple. Each thread which want to touch mutex, will create IVar, atomically swap it in the IORef masked (note, that IORef’s operations non-blocking), unmask and wait for previous become available IVar *unmasked*. Then it will either perform it’s operations or fail due to the interruption or exception and trigger newly installed IVar anyway. It just works. Without any «interruptible» magic.

So, which benefits can we get?

1.       Simpler implementation of basic primitives. Obliviously IORef is fairly simple. IVar is also mind be simpler than MVar, and possibly faster (just “possibly”, I don’t know how it’s implemented, but I guess lesser state transitions implies less logic).

2.       Simplified deadlock analysis. Really, we have only IVar with only one state transition and only one potentially blocking operation.

3.       Magicless support of interruptions. We don’t need to separate mask/uninterruptibleMask anymore, because all updates are non-blocking, and all waits are unmasked. 

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.