RE: Re[2]: important news: refocusing discussion

On 28 March 2006 00:24, Ross Paterson wrote:
On Mon, Mar 27, 2006 at 09:36:28AM +0100, Simon Marlow wrote:
The portable interface could be Control.Concurrent.MVar, perhaps.
As Malcolm pointed out, using MVars requires some care, even if you were just aiming to be thread-safe.
I don't really understand the problem, maybe I'm missing something. I thought the idea would be that a thread-safe library would simply use MVar instead of IORef. So instead of this: do x <- readIORef r ... writeIORef r x' you would write do modifyMVar_ r $ \x -> ... return x' actually the second version is not only thread-safe, but exception-safe too. Malcolm's objections:
But Q2 explicitly raises the issue of whether a non-concurrent implementation must still follow a minimum API. That could be a reasonable requirement, if we fleshed out the detail a bit more. The specific suggestion of requiring MVars makes me a tiny bit worried though. After all, MVars capture the idea of synchronisation between threads, and I assume that since a non-concurrent implementation has only one thread, that thread will be trying to MVar-synchronise with something that does not exist, and hence be blocked for ever. I can imagine that there are situations where synchronisation is entirely safe and free of blocking, but how to specify when it would be unsafe?
There's no synchronisation, because we're not writing multi-threaded code here. Just code that doesn't have any race conditions on its mutable state when run in a multi-threaded setting. Maybe you could elaborate on what problems you envisage? Back to Ross:
How about STM (minus retry/orElse) and TVars as the portable interface? They're trivial for a single-threaded implementation, and provide a comfortable interface for everyone.
Now that's a rather good idea. It does raise the bar for the concurrent implementations, though, and STM is not nearly as mature and well-understood as MVars. There do exist implementations of STM in terms of MVars (at least two I know of). Cheers, Simon

"Simon Marlow"
The portable interface could be Control.Concurrent.MVar, perhaps.
I don't really understand the problem, maybe I'm missing something. I thought the idea would be that a thread-safe library would simply use MVar instead of IORef.
I was misled by several people's hand-waving assertion that, provided you used MVars "correctly", there would be no synchronisation problems. But no-one had yet defined what "correct" meant. I kind of assumed they meant you could write concurrent threaded code (with only some minor restrictions) and have it work in a single-threaded implementation without change. This seemed like a pretty strong (and dubious) claim to me. But now I see you are actually saying something quite different. (And I recall some discussion on these points from a few months ago.) * IORef is inherently thread-unsafe, and so we should eliminate IORefs from the language. * One can write single-threaded code using MVars instead of IORefs, and it will be safe on a multi-threaded implementation. The latter point is quite the opposite of what I thought was being proposed. Regards, Malcolm

On Tue, 2006-03-28 at 11:05 +0100, Malcolm Wallace wrote: (snip)
* IORef is inherently thread-unsafe, and so we should eliminate IORefs from the language.
That's not quite true, as you can have an IORef guarded by an MVar. Why would you want such a thing? For instance, you might write a library with two IORefs and one MVar instead of two MVars in order to reduce the possibility of deadlock. Is it the case that a library is thread-safe as long as it doesn't use IORefs, though? I trolled around base looking for libraries that might not be thread-safe and found only that HashTable uses an IORef, and indeed there's a FIXME that says it should use an MVar. I didn't look very hard, though. peace, isaac

On Tue, Mar 28, 2006 at 11:05:03AM +0100, Malcolm Wallace wrote:
I was misled by several people's hand-waving assertion that, provided you used MVars "correctly", there would be no synchronisation problems. But no-one had yet defined what "correct" meant. I kind of assumed they meant you could write concurrent threaded code (with only some minor restrictions) and have it work in a single-threaded implementation without change. This seemed like a pretty strong (and dubious) claim to me.
it is true if you omit forkIO. as in, any use of MVars that cannot dead-lock and doesn't use forkIO in a concurrent setting, can't deadlock in a non-concurrent one. it is possible to write programs that only won't deadlock in a concurrent setting, but _only_ if you explicitly use forkIO at some point so we still have our static guarentees about deadlocks.
But now I see you are actually saying something quite different. (And I recall some discussion on these points from a few months ago.)
* IORef is inherently thread-unsafe, and so we should eliminate IORefs from the language.
dear golly no. that is part of the point of IORefs, the memory barriers needed by MVars are _extremely_ expensive compared to the simple memory accesess that IORefs should encompass, you don't want to use MVars except when absolutly needed. when using IORefs it is your responsibility to ensure they are used solely from within a single thread, the same way it is your responsibility to not deadlock when using MVars.
* One can write single-threaded code using MVars instead of IORefs, and it will be safe on a multi-threaded implementation.
but much much less efficient on multi-threaded systems if you knew the refs would only be used in a single threaded manner (as is the case for most uses of iorefs) We do need to say something about the distinction between IORefs and MVars in the report, but getting rid of IORefs would be a horrible mistake if we want our concurrent implementations to be fast. if nothing else, we lose the intent of the programmer. I would even go so far as to disallow the IORef protected by MVar usage model just to be future-safe. but that might be too unpopular and STM sort of makes it less important to worry about. John -- John Meacham - ⑆repetae.net⑆john⑈

On Tue, Mar 28, 2006 at 10:14:27AM +0100, Simon Marlow wrote:
On 28 March 2006 00:24, Ross Paterson wrote:
As Malcolm pointed out, using MVars requires some care, even if you were just aiming to be thread-safe.
I don't really understand the problem, maybe I'm missing something. I thought the idea would be that a thread-safe library would simply use MVar instead of IORef.
MVars certainly require more care than IORefs: you have to ensure your takes and puts are matched, for example. And there's the possibility of deadlock when you have more than one variable. I was toying with an interface like newRef :: a -> IO (Ref a) modifyRef :: Ref a -> (a -> (a, r)) -> IO r modifyRef2 :: Ref a -> Ref b -> (a -> b -> (a, b, r)) -> IO r ... where Refs are MVars plus a stable ordering, so all the primitives lock (i.e. take) them in the same order. It's a bit clunky, though. On Tue, Mar 28, 2006 at 10:25:04AM +0100, Simon Marlow wrote:
It just occurred to me that STM isn't completely trivial in a single-threaded implementation, because exceptions have to abort a transaction in progress.
Ah, and it seemed so simple. Still, exception-safety would be a nice property for a state abstraction to have.
participants (5)
-
isaac jones
-
John Meacham
-
Malcolm Wallace
-
Ross Paterson
-
Simon Marlow