RE: New Bound Threads Proposal
| >> *) It violates requirement #4 "The specification shouldn't explicitly | >> require lightweight "green" threads | >> to exist." | >> OK, I wrote that requirement, and I might be the only one who cares. | >> But I still think that the existance of lightweight threads should not | >> somehow become a feature of the Haskell language. | > | > Not a feature of Haskell 98, but we are talking about a | > concurrency extension anyway. In fact, I think, we should | > have an Addendum that covers all concurrency support | > (lighweight and OS threads) in one go. If anything, this is | > only going to make the overall system simpler. This is a good point, and one we hadn't really understood before. I thought that P3 required green threads even less than P1 and P2, but it's quite the reverse, which is a bit counter intuitive. Thus motivated, I've sketched Proposal 4, which is a sort of combinatory of P2 and P3. Much simpler than P2, but addressing the concerns Wolfgang raises. The main difference relative to P3 is that a Haskell thread is either unbound or bound to a native thread N Like P2, it has the property that at most one Haskell thread can be bound to a native thread N. Also like P1 and P2, the unbound Haskell threads can be executed one-per-native-thread, or by a pool of worker native threads, as you please. Unlike P2, there is no notion of an "associated" native thread, or of "temporary binding" of a Haskell thread to a native thread. Details in CVS, and in a Postscript file that I'll send separately (I'm having trouble with my externally-visible web site). Wolfgang, does this help? Simon | | It would be the first place where implementations of Concurrent Haskell | are required to provide lightweight threads. Also, I expect Concurrent | Haskell to be here to stay for some time, so tying the concurrency | extension to the implementation technique that happens to be "standard" | right now would IMHO still be a mistake. | | >> *) Using the system requires a lot of knowledge of the thread | >> allocation issues, as all thread management has to be done manually. | >> Special combinators like nonBlocking will have to be used far more | >> often than the "threadbound" combinator of proposal #2. | > | > The FFI design so far has always tried to provide minimal | > extensions. | | Haskell so far has tried to be "safe" to use; this manual thread | management is not safe, it smells like it's going to generate lots of | bugs. This would be the first time that Haskell (+concurrency | extension+libraries) is less safe than C (+OS libraries) in a | particular area (threading). You just don't get that strange blocking | behaviour in C. | | It could even be argued that Proposal 3 is, in fact, not a minimal | extension at all; If we have a multi-OS-thread Haskell implementation, | we can implement Proposal 2 by | threadbound = id | ... but Proposal 3 can only be implemented by changing to a | light-weight-thread based scheduler. | | The explicit thread management of Proposal 3 requires run time systems | to support situations where 3 Haskell threads are running in OS thread | A, and two more Haskell threads are running in OS thread B. Who needs | that, and what for? | It also requires runtime systems to actually use the native thread that | a Haskell thread is bound to for it's execution (which happens to be | the natural choice for GHC's implementation). | | It should be possible to implement Proposal 2 for Hugs; my current | impression is that Hugs' Concurrency implementation would have to be | redone from scratch to support Proposal 3. | | Cheers, | | Wolfgang |
Thus motivated, I've sketched Proposal 4, which is a sort of combinatory of P2 and P3. Much simpler than P2, but addressing the concerns Wolfgang raises.
The main difference relative to P3 is that a Haskell thread is either unbound or bound to a native thread N
Like P2, it has the property that at most one Haskell thread can be bound to a native thread N. Also like P1 and P2, the unbound Haskell threads can be executed one-per-native-thread, or by a pool of worker native threads, as you please.
Unlike P2, there is no notion of an "associated" native thread, or of "temporary binding" of a Haskell thread to a native thread.
Details in CVS, and in a Postscript file that I'll send separately (I'm having trouble with my externally-visible web site).
Wolfgang, does this help?
Yes, it definitely helps. It seems to be equivalent to "P1, but all foreign exports are bound". Some remarks: *) forkOS needn't be a primitive, it can be implemented via the FFI (cf Section 3.2, first Proposal). *) The semantics should probably have an (END) rule that specifies that a thread created using forkIO can just end (without returning anywhere). *) We should perhaps add a primitive for querying whether the current thread is bound or not. This can be used for writing combinators that use forkOS only when necessary to make sure that we can access a foreign API that uses thread-local state. *) I don't think that a bindToOS primitive is really necessary, emulating it using forkIO shouldn't be noticably slower, IMO. *) We might sacrifice a little performance by making all foreign exports "bound". That should be paid back in simplicity. Hardcore hackers can always use forkIO to move work to a lightweight thread. One issue remains however (and that's the topic of the rest of this e-mail); what to do about broken libraries that treat the "main" OS thread (i.e. the one that static initializers are run in) as special? This really happens; in fact both of Apple's GUI APIs are that brain-dead in the current version of Mac OS X. In P2, that was the reason for "associating" the "main" Haskell thread to the "main" IO thread. The costs for that are negligible in P2, but _binding_ the "main" Haskell thread to the "main" OS thread might be overkill (or it might not be, see below). If we don't bind the main thread, we will need some other way to get access to it: For the GHC implementation, it would probably be possible to "trick" the implementation into using the "main" OS thread using something like this: main = main' foreigh import "foo" main' :: IO () foreign export "foo" main'' :: IO () main'' = ... _IF_ the implementation behaves the way I think the GHC implementation will, then main'' will be bound to the "main" OS thread. (The FFI business could of course be put in a combinator useMainOSThread or something). But that doesn't necessarily work for all implementations, so perhaps we'd have to add it to the semantics somehow... Now if we do bind the main thread, what are the costs? If we had main = forkIO doLongComputation >> doAnotherLongComputation there'd be OS thread switching going on. To optimize that, we'd have to write main = do forkIO doLongComputation mv <- newEmptyMVar forIO (doAnotherLongComputation >> putMVar mv ()) takeMVar mv ... so that there'd be no OS thread switching. So, our options seem to be 1) bind the "main" Haskell thread to the "main" OS thread 2) don't bind the "main" Haskell thread to the "main" OS thread 2a) complicate the system by adding a way to access the "main" OS thread 2b) keep the system simple, use the hack I described above for GHC programs that use Apple's libraries, and hope that Apple fixes it's libraries before people want to use them with other implementations of proposal4 where the hack doesn't work. OK, that's all.. Cheers, Wolfgang
On Tue, May 06, 2003 at 03:19:17PM +0200, Wolfgang Thaller wrote:
1) bind the "main" Haskell thread to the "main" OS thread 2) don't bind the "main" Haskell thread to the "main" OS thread 2a) complicate the system by adding a way to access the "main" OS thread 2b) keep the system simple, use the hack I described above for GHC programs that use Apple's libraries, and hope that Apple fixes it's libraries before people want to use them with other implementations of proposal4 where the hack doesn't work.
I don't see any problem with 1. It is no worse than the situation in other languages and is significantly better in that Haskell can easily pass around IO actions for the main thread to run. plus, any library documentation for other languages will carry over into Haskell easily. I can imagine something like this: (pseudo-ish code) main = do forkIO restOfApp mainLoop restOfApp = do .. .. x <- runInMain sillyFuncion .. .. runInMain :: IO a -> IO a runInMain action = do v <- newMVar writeChan mainChannel (action >>= putMVar v) takeMVar v {-# NOTINLINE mainChannel #-} mainChannel :: Chan (IO ()) mainChannel = unsafePerformIO (newChan) mainLoop = repeatM_ $ readChan mainChannel >>= \x -> x I did something similar for a UI library where actions needed to be serialized (and sometimes dropped/combined, which is why i didn't use a simple lock) and it worked well. John -- --------------------------------------------------------------------------- John Meacham - California Institute of Technology, Alum. - john@foo.net ---------------------------------------------------------------------------
John Meacham wrote:
On Tue, May 06, 2003 at 03:19:17PM +0200, Wolfgang Thaller wrote:
1) bind the "main" Haskell thread to the "main" OS thread [...] I don't see any problem with 1. It is no worse than the situation in other languages and is significantly better in that Haskell can easily pass around IO actions for the main thread to run. plus, any library documentation for other languages will carry over into Haskell easily.
Does anyone else have objections against always binding the "main" Haskell thread to the "main" OS thread? The thing is, if we always make the main thread bound, the implementation in the GHC RTS becomes simpler and much more "beautiful". Any possible performance penalty due to the use of a bound thread can be easily worked around like this: main = do finish <- newEmptyMVar forkIO (realMain >> putMVar finish ()) takeMVar finish <technical details> A non-bound "main" thread would be the only non-bound thread that has to return somewhere. In the previous threaded RTS, this was solved by keeping a global variable "mainMainThread"; when that thread ends, the RTS does a "threadsafe" foreign call to shutdownHaskellAndExit. Also there's that strange distinction between rts_evalLazyIO and rts_mainLazyIO in order to avoid spawning another OS thread. On program termination, shutdownHaskellAndExit has to make sure that there's a second worker thread anyway, because it can't use rts_mainLazyIO for executing finalizers. All those hacks would go away if the "main" Haskell thread is always bound to the "main" IO thread. </technical details> Cheers, Wolfgang
participants (3)
-
John Meacham -
Simon Peyton-Jones -
Wolfgang Thaller