
Hi everyone, I have been confused by some things about threads for a long time. I'm hoping someone out there can help clear this up. I'll clean up and document on the wiki if we get conclusive answers. So it seems there are four scenarios for firing off threads: A) Threaded RTS, forkIO B) Threaded RTS, forkOS C) Non-threaded RTS, forkIO D) Non-threaded RTS, forkOS So the questions, for each of the four models, are: 1) What is the impact of firing off a thread to execute a pure (non-IO) computation under each model? Will multiple pure computations be allowed to run in parallel, or will only one run at a time? (While the computation may be outside the IO monad, of course at the end it will have to use IO to communicate the result back.) 2) What is the impact of IO under each model? Will GHC internally use select/poll/whatever? Or will each thread get a dedicated OS thread that uses synchronous I/O? 3) When signals are received, which thread receives them? 4) When forkProcess is executed, which thread(s) are duplicated to the forked process? 5) What does an FFI import "safe" mean under each model? 6) What does an FFI import "unsafe" mean under each model? 7) What is the expected future level of support for each model? This is of significant concern to me, as it appears that the threaded RTS is only supported on an extremely limited set of architectures (most programs won't even link on Debian's autobuilders if I use -threaded). Also I have heard comments that the non-threaded RTS may be dropped in the future. 8) What is the expected level of support for STM in combination with each threaded model? 9) How does par mix with each threaded model? Is it equivolent to forkOS or forkIO? Thanks, -- John

On Sun, Jul 29, 2007 at 05:35:26PM -0500, John Goerzen wrote:
Hi everyone,
I have been confused by some things about threads for a long time. I'm hoping someone out there can help clear this up. I'll clean up and document on the wiki if we get conclusive answers.
So it seems there are four scenarios for firing off threads:
A) Threaded RTS, forkIO B) Threaded RTS, forkOS C) Non-threaded RTS, forkIO D) Non-threaded RTS, forkOS
So the questions, for each of the four models, are:
1) What is the impact of firing off a thread to execute a pure (non-IO) computation under each model? Will multiple pure computations be allowed to run in parallel, or will only one run at a time? (While the computation may be outside the IO monad, of course at the end it will have to use IO to communicate the result back.)
A) B) Parallel C) D) Sequential
2) What is the impact of IO under each model? Will GHC internally use select/poll/whatever? Or will each thread get a dedicated OS thread that uses synchronous I/O?
GHC uses select/poll/whatever under all four models.
3) When signals are received, which thread receives them?
GHC never sends signals to threads. Signals are handled by creating a brand new thread to run your handler.
4) When forkProcess is executed, which thread(s) are duplicated to the forked process?
A) B) errorBelch("forking not supported with +RTS -N<n> greater than 1"); stg_exit(EXIT_FAILURE) C) D) Only the current thread (note, this means that the thread which serves Handle IO won't exist and said functions will lock)
5) What does an FFI import "safe" mean under each model?
A) C) A new OS-level thread is forked from a pool and the code is executed in it. B) D) A dedicated OS-level thread is forked *at forkOS time*, and used for all safe foreign calles in the thread. This is of course much more expensive, but makes TLS-using libraries like OpenGL work.
6) What does an FFI import "unsafe" mean under each model?
A) B) C) D) The code is executed inline (after saving caller-saves registers, of course)
7) What is the expected future level of support for each model? This is of significant concern to me, as it appears that the threaded RTS is only supported on an extremely limited set of architectures (most programs won't even link on Debian's autobuilders if I use -threaded). Also I have heard comments that the non-threaded RTS may be dropped in the future.
A) B) High. C) D) Unknown. It depends on whether the threaded RTS can be made to interact in a remotely sane way with low level Unix programming (cf HSH).
8) What is the expected level of support for STM in combination with each threaded model?
A) B) Good. Atomic instructions are used, along with a traditionally greedy-transactions model. C) D) Very good. No atomic instructions are needed, since the system simply refrains from preeemting during individual primops, including the commit.
9) How does par mix with each threaded model? Is it equivolent to forkOS or forkIO?
A) B) Neither, it's a very low level operation which adds the node to a pool of things which should be executed. The spark pool is read from when the runqueue is empty, and will not grow without bound since it's circular (old sparks are discarded) C) D) newSpark is a noop
Thanks, John
Notice that A/B and C/D are virtually equivalent. forkOS has *no effect* on anything but safe foreign calls. Stefan

On Sun, 2007-07-29 at 17:35 -0500, John Goerzen wrote:
Hi everyone,
I have been confused by some things about threads for a long time. I'm hoping someone out there can help clear this up. I'll clean up and document on the wiki if we get conclusive answers.
So it seems there are four scenarios for firing off threads:
A) Threaded RTS, forkIO B) Threaded RTS, forkOS C) Non-threaded RTS, forkIO D) Non-threaded RTS, forkOS
If I recall correctly, D throws a runtime error because the guarantees that forkOS is supposed to provide are impossible without the threaded rts. (I think) You generally do not want forkOS. It's really only for wierd foreign libs that require that they be called from the same OS thread every time eg because they keep thread local state (like OpenGL). Using forkOS will get you no extra parallelism. You get parallelism linking with the threaded rts and running your program using multiple capabilities. http://haskell.org/ghc/docs/latest/html/users_guide/sec-using-smp.html
So the questions, for each of the four models, are:
1) What is the impact of firing off a thread to execute a pure (non-IO) computation under each model? Will multiple pure computations be allowed to run in parallel, or will only one run at a time? (While the computation may be outside the IO monad, of course at the end it will have to use IO to communicate the result back.)
You only get parallelism (as opposed to concurrency) of pure code when using the threaded rts, and then only when running the program using more than one capability (+RTS -N2 -RTS).
2) What is the impact of IO under each model? Will GHC internally use select/poll/whatever? Or will each thread get a dedicated OS thread that uses synchronous I/O?
In both ghc only uses on OS thread for IO. In the threaded rts it's an *additional* OS thread but it's still only one. In the single threaded rts, it's the rts that does the select/poll. In the threaded rts it's a Haskell IO manager thread that uses select/poll on behalf of other Haskell threads that need to block until the completion of I/O.
3) When signals are received, which thread receives them?
Each signal gets handled by a new unbound Haskell thread.
4) When forkProcess is executed, which thread(s) are duplicated to the forked process?
Only the calling one. All other Haskell threads disappear.
5) What does an FFI import "safe" mean under each model?
single-threaded: all Haskell threads block until the foreign call returns. multi-threaded: other Haskell threads continue in parallel.
6) What does an FFI import "unsafe" mean under each model?
single-threaded: all Haskell threads block until the foreign call returns. multi-threaded: other Haskell threads in the same 'capability' block until the foreign call returns. If the program is using more than one capability then Haskell threads in the other capabilities should continue to run. In both cases, unsafe should only be used for short-running, non-blocking foreign calls that do not make callbacks into Haskell.
7) What is the expected future level of support for each model? This is of significant concern to me, as it appears that the threaded RTS is only supported on an extremely limited set of architectures (most programs won't even link on Debian's autobuilders if I use -threaded). Also I have heard comments that the non-threaded RTS may be dropped in the future.
The non-threaded rts is not going to get many improvements though it probably will not be dropped while the threaded rts doesn't work on those other arches. The threaded rts will probably become the default in some upcoming release.
8) What is the expected level of support for STM in combination with each threaded model?
No idea, but bear in mind the threaded rts is where the attention is going.
9) How does par mix with each threaded model? Is it equivolent to forkOS or forkIO?
forkIO. Duncan

On Mon, 30 Jul 2007, Duncan Coutts wrote: ...
You generally do not want forkOS. It's really only for wierd foreign libs that require that they be called from the same OS thread every time eg because they keep thread local state (like OpenGL).
Can Haskell application code run in a thread created by the foreign library? I'm assuming some requirement for C level API to bootstrap the new thread in the runtime. ...
In both ghc only uses on OS thread for IO. In the threaded rts it's an *additional* OS thread but it's still only one.
In the single threaded rts, it's the rts that does the select/poll. In the threaded rts it's a Haskell IO manager thread that uses select/poll on behalf of other Haskell threads that need to block until the completion of I/O.
Can my own application's foreign I/O plug into that IO manager? Does select work for all I/O? For example, take DNS - Network.BSD.getHostByName could involve some network I/O and a lengthy delay, but as far as I know there isn't any select-friendly interface even at the lower levels. Or System.Posix.getProcessStatus, which doesn't generate any I/O in the sense of a selectable device. Donn Cave, donn@drizzle.com
participants (4)
-
Donn Cave
-
Duncan Coutts
-
John Goerzen
-
Stefan O'Rear