Sorry if you don't want to be bothered with my problems, but I think this
problem which I've just encountered is rather amusing. Is there a neat solution?
I confess to finding concurrency problems difficult so there might be.
I want to implement a type Flag. Each flag is either Up or Down. When you
create a flag (there is a newFlag :: IO Flag operation) it is Up. The only
operation permitted on Flags is
lowerFlags :: Flag -> Flag -> IO Bool
If both arguments are Up, lowerFlags should lower them both and return
True, otherwise it should leave them as they are and return False.
The problem is that lowerFlags should appear to be atomic, so that
if lowerFlags is called simultaneously in different threads, the result
should be the same as if one of the calls completed before the other started.
So for example the following code
type Flag = MVar Bool -- True means Up
newFlag = newMVar True
lowerFlags flag1 flag2 =
if (flag1 == flag2)
then
error "Illegal call to lowerFlags" -- this is not allowed
else
do
val1 <- takeMVar flag1
-- point A
val2 <- takeMVar flag2
let success = (val1 && val2)
putMVar flag2 (not success && val2)
putMVar flag1 (not success && val1)
won't work, because if lowerFlags is called simultaneously with the same arguments
but in a different order, and both calls reach point A simultanously, then both
threads will block when they attempt the takeMVar in the next line.
What is the neatest solution? There is an obvious solution, which is to
crudely sequence all calls to lowerFlags by making them lock a single
global variable (created using, sigh, unsafePerformIO) but this doesn't seem very
elegant. If MVar's were instances of Ord as well as Eq, a neat solution would
be to always get the least MVar first, but they aren't. So what should one do?