RE: Native Threads in the RTS

Improving. Want to put it in CVS? Simon M can suggest where. Simon | Definitions | ~~~~~~~~~~~ | A native thread (aka OS thread) is a thread as defined by the operating | system. | A Haskell thread is [*** FIXME - How shall I put this? ***] the thing | you see from Haskell land. A "Haskell thread" encapsulates the execution of a Haskell I/O action. A Haskell thread is created by forkIO, and dies when the I/O action completes. A Haskell thread is always executed by a native thread. The Haskell RTS creates one or more "worker native threads" to execute Haskell threads. | Haskell threads may be associated at thread creation time with either | zero or one native threads. Each Native thread is associated with zero | or more native threads. zero or more *Haskell* threads. Can you give an example of when a native thread is associated with more than one Haskell thread? To avoid "A Haskell thread associated with a native thread" I'd prefer to define the term "a bound Haskell thread". I would also like to describe a "bound native thread" as one that has associated Haskell thread(s). | If a native thread is associated with one or more Haskell threads, | exactly one of the following must be true: | *) Exactly one Haskell thread associated with the native thread is | executing. | *) The native thread is executing foreign code. | *) The native thread and all Haskell threads associated with it are | blocked. You don't say (but you do mean) A bound Haskell thread can be executed only by its associated native thread You don't say (and I'm not sure if you mean) If a bound native thread blocks, all of its associated Haskell threads are blocked too If a bound Haskell thread blocks, its associate native thread and all its associated Haskell threads also block. | The thread that main runs in, threads created using forkIO and threads | created for running finalizers or signal handlers are not necessarily | associated with a native thread. However, an implementation might | choose to do so. But the impl may *not* choose a bound native thread. These must be kept inviolate. | When a "free" foreign exported function is invoked, the implementation | may freely choose what kind of Haskell thread the function is executed | in. It is not specified whether this thread is associated with a | particular OS thread or not. Again, it must not be a bound native thread. | When a foreign imported function is invoked [by Haskell code], the | foreign code is executed in the native thread associated with the | current Haskell thread, if an association exists. If the current | Haskell thread is not associated to a native thread, the implementation | may freely decide which thread to run the foreign function in. | The existing distinction between unsafe, safe and threadsafe calls | remains unchanged. If a bound Haskell thread calls a foreign import that is not labelled 'threadsafe' which calls a bound foreign export does that work? What if the foreign export was not bound? Similarly, if the foreign import was labelled 'threadsafe', would it work? It's not obvious to me. Some kind of semantics would be good.

I'll write up a new version of the proposal tomorrow. For now, here are some answers and [at the end] a question about the current FFI specification. Simon Peyton Jones wrote:
You don't say (but you do mean)
A bound Haskell thread can be executed only by its associated native thread
No I don't mean that, and I've been avoiding to say that. This is how the GHC implementation will probably handle it, but I do not want to put that in the general specification. Alastair remarked a while ago:
For that matter, I'd like it to be possible to implement this spec in Hugs. Hugs is internally single-threaded but this spec is concerned with what happens when Haskell calls out to C and we could arrange to switch into the appropriate native thread when Hugs calls out to C.
After all, the only thing which needs to be guaranteed is that foreign functions called by the Haskell thread are executed in the associated native thread.
You don't say (and I'm not sure if you mean)
If a bound native thread blocks, all of its associated Haskell threads are blocked too
If a bound Haskell thread blocks, its associate native thread and all its associated Haskell threads also block.
Does this sound clearer: *) Exactly one Haskell thread associated with the native thread is executing. All other associated Haskell threads are blocked. No foreign code is being executed by the native thread. *) The native thread is executing foreign code. No Haskell code is executing in any of the associated Haskell threads. *) The native thread and all Haskell threads associated with it are blocked.
| The thread that main runs in, threads created using forkIO and threads | created for running finalizers or signal handlers are not necessarily | associated with a native thread. However, an implementation might | choose to do so.
But the impl may *not* choose a bound native thread. These must be kept inviolate.
[...] Again, it must not be a bound native thread.
Good point, I had overlooked that.
If a bound Haskell thread calls a foreign import that is not labelled 'threadsafe' which calls a bound foreign export does that work? What if the foreign export was not bound?
Similarly, if the foreign import was labelled 'threadsafe', would it work? It's not obvious to me. Some kind of semantics would be good.
Good question. I reread Section 3.3 of the FFI document (RC7), and now I think I cannot clarify my specification in this respect without first asking others to clarify the current specs - can someone explain the distinction between unsafe, safe and threadsafe in the current FFI to me? I think I know what it does in GHC, but what's the general definition? I've read the description in the FFI document, but it's not clear to me. Is there any reason why "safe" is the default and not "threadsafe"? After all, "safe" is less safe (it might cause the whole program to block). To me, "safe" seems to be an odd middle ground between speed and safety. What is "safe" guaranteed/allowed to do? Is it _guaranteed_ to block other Haskell threads under certain conditions? Or is that only an artifact of current implementations? Why are implementations allowed to _silently_ fall back to "safe" when "threadsafe" is not supported? Isn't that dangerous? If I'm not mistaken, "threadsafe" calls from bound Haskell threads would have exactly the same overhead as "safe" calls. Should we make sure that "safe" calls somehow block other threads? If so, why? Thats all for now.... Wolfgang

[Note: I'm consistently using 'foreign thread' instead of 'native thread'. The Haskell-spec necessarily treats Haskell as the centre of the universe. So what a Linux kernel hacker might think of as a 'native thread' is really quite foreign to Haskell. Feel free to ignore this little experiment with the language.]
*) Exactly one Haskell thread associated with the native thread is executing. All other associated Haskell threads are blocked. No foreign code is being executed by the native thread.
This isn't quite right - or, at least, needs clarified. Consider Haskell functions a,b,c,d and C functions A,B,C,D and a call pattern a -> A -> b -> B -> c -> C -> d -> D That is, a calls A, calls b, calls B, calls ... Suppose we want A,B,C,D executed by the same foreign thread. Each of a,b,c,d are executed by different Haskell threads (because a new Haskell thread is spawned for each call into Haskell) so we have multiple Haskell threads associated with a single foreign thread. Now, when D is called, which of these threads is 'executing' and which are 'blocked'? I think the quoted text assumes that a,b,c,d are blocked during the call to D. The GHC implementation might well perform a context switch on making the calls into C so we could, perhaps, say that a Haskell thread is 'blocked' while making an ffi call. But, for normal function calls (i.e., all in Haskell or all in C, no ffi stuff), we don't say that the caller is 'blocked' until the callee returns. I think it's better to reserve the word is 'blocked' for its normal usage and avoid possibly over-specifying what implementations must do to implement the spec. Possible rewording: Definitions: Let $f$ be a foreign thread. $uses(f)$ is the number of foreign calls by Haskell threads bound to $f$. $bindees(f)$ is the number of Haskell threads bound to $f$. Invariant: $0 <= bindees(f) - uses(f) <= 1$ Proof: (Should be possible by) structural induction over relevant IO operations (forkIO, forkNativeIO, etc.) This needs careful interpretation if we want to be able to bind finalizers to foreign threads. In particular, if a finalizer is bound to a foreign thread, we don't increment 'bindees(f)' until the finalizer starts and we don't start the finalizer unless either: bindees(f) - uses(f) == 0 or, maybe even, bindees(f) == 0 Or, we can adopt a much weaker semantics than Wolfgang intended and have: $0 <= bindees(f) - uses(f)$ This would allow several currently running, active Haskell threads to all be bound to the same foreign thread. When any of these threads makes a foreign call, the other threads could all keep running and they would only block if they too tried to make foreign calls. From an implementation point of view, this requires: 1) That foreign threads are _not_ used to execute Haskell code. 2) That we maintain a lock on foreign threads so that only one Haskell thread tries to use it at a time. I see two potential problems with this (but would like to hear which, if either, dominates your thoughts): 1) If foreign threads cannot be used to execute Haskell code, foreign calls require (OS-level) context switches which are expensive. 2) Adding an implicit lock to foreign calls might surprise programmers. -- Alastair

On 29 Nov 2002, Alastair Reid wrote:
Consider Haskell functions a,b,c,d and C functions A,B,C,D and a call pattern
a -> A -> b -> B -> c -> C -> d -> D
That is, a calls A, calls b, calls B, calls ...
Suppose we want A,B,C,D executed by the same foreign thread.
Each of a,b,c,d are executed by different Haskell threads (because a new Haskell thread is spawned for each call into Haskell) so we have multiple Haskell threads associated with a single foreign thread.
It doesn't feel right to me that a,b,c,d are executed by different Haskell threads. Why shouldn't they all be the *same* Haskell thread? 1. Reasonable exception handling across the Haskell/foreign boundary is not currently supported, but if we imagine moving in that direction, it would seem we'd want a single thread for the given example. 2. Calling from foreign code into Haskell to a bound foreign import will require some special handling to ensure that a subsequent call out to foreign code will use the same native thread. Why couldn't this special handling select the same Haskell thread instead of creating a new one? Dean
participants (4)
-
Alastair Reid
-
Dean Herington
-
Simon Peyton-Jones
-
Wolfgang Thaller