Yesterday evening I was experimenting with an electronic circuit, and the fumes from the soldering must have entered my brain and triggered something there - I've thought up a new proposal, which I like much better than the old one. It no longer requires extending the FFI syntax; instead, it uses just one new combinator. It can be implemented as efficiently as the previous proposal. The proposal is described in detail in the new "Part II" of the haskell-report/ffi/threads.tex document (in CVS). For everyone's convenience, I've put up a typesetted version again at:
http://www.uni-graz.at/imawww/haskell/threads.pdf
Everyone please tell me what you think.
I've not had the benefit of soldering fumes, but I find whiteboard markers work quite well ;-) I think I like it. Just to clarify: when you fork a new Haskell thread, it isn't associated with a native thread, right? (the semantics doesn't model forkIO, I think it probably should). If true, that gives us the important principle that At any one time, there is at most one runnable Haskell thread associated with, or bound to, any given native thread. which is important for reducing the complexity of the implementation. In an implementation which actually runs the Haskell thread in its bound native thread (eg. GHC), we can be sure that when we want to switch to the bound native thread it isn't off doing something else. Is that the reason for wanting this property? Cheers, Simon
I think I like it. Just to clarify: when you fork a new Haskell thread, it isn't associated with a native thread, right?
Right. I said so in \subsection{Initial State}.
(the semantics doesn't model forkIO, I think it probably should).
OK, I'll add it to the semantics.
If true, that gives us the important principle that
At any one time, there is at most one runnable Haskell thread associated with, or bound to, any given native thread.
Exactly. I'll make sure it's mentioned somewhere. (Also, a Haskell thread cannot be bound to a native thread that already has a different Haskell thread associated with it).
which is important for reducing the complexity of the implementation. In an implementation which actually runs the Haskell thread in its bound native thread (eg. GHC), we can be sure that when we want to switch to the bound native thread it isn't off doing something else. Is that the reason for wanting this property?
Yes, that's what I was thinking. I think I'll try to implement a prototype soon (unless someone protests quickly and loudly), because I have no courses at the university for one more week (3 weeks of Easter break), so I have some free time that I won't have afterwards. Cheers, Wolfgang
Hi all, Shame on me to not have written a proposal yet, but I am still too busy with other stuff right now.
I think I'll try to implement a prototype soon (unless someone protests quickly and loudly),
As you might guess, I am really happy to see the combinator approach and an implementation in Haskell. (I still have to look a the details of your proposal though). If you start with the prototype, I urge you to build it completely in Haskell; using the *minimal* amount of primitive operations required. Those operations will probably be: - fork a native thread - attach a haskell thread to a native thread - moving unbound threads to other native threads - getting the current threads and their mapping to native threads Secondly, try to remove the whole safe/unsafe/threadsafe business and make "safe" and "threadsafe" combinators too. Success, Daan.
Shame on me to not have written a proposal yet, but I am still too busy with other stuff right now.
I understand that... I'll probably be too busy again in a little more than a week...
If you start with the prototype, I urge you to build it completely in Haskell;
I was not talking about a "simulator", I was talking about a first draft of a working implementation for GHC. I would implement it in C, because it would have to be part of the RTS. After all, it's part of the scheduler, and the scheduler is already implemented in C.
using the *minimal* amount of primitive operations required. Those operations will probably be: - fork a native thread - attach a haskell thread to a native thread - moving unbound threads to other native threads - getting the current threads and their mapping to native threads
Question: Do you want them as part of a "low-level" public interface, i.e. should they be the same accross all Haskell implementations that implement your proposal? If so, that contradicts two of the goals I've started from: (*) The specification shouldn’t explicitly require lightweight “green” threads to exist. The specification should be implementable in a simple and obvious way in haskell systems that always use a 1:1 correspondence between Haskell threads and OS threads. (*) The specification shouldn’t specify which particular OS thread should be used to execute Haskell code. It should be possible to implement it with e.g. a Haskell interpreter running in one OS thread that just uses other OS threads for foreign calls. (These are the fourth and fifth points of section 2, "Requirements", of the threads document) This is why I oppose having these, or similar operations, as part of any standard public interface. Now if you mean using these primitives as an interface between the GHC RTS and GHC-specific libraries... well at least I'm less opposed to it. I think it's more work to expose all these primitives. These primitives actually provide more features than we need (we don't need to explicitly map unbound threads to native threads; a scheduler loop running in an [unassociated] native thread can just execute any unbound thread without much ado). Also, it feels like splitting up the scheduler logic in half between the RTS and the libraries. We don't we have primitive Haskell action for "add to run queue", "remove from run queue" etc. so that we can implement all the scheduling and MVars and so on in Haskell - so why should we implement just this part of the scheduler in Haskell? Thirdly, I fear that it would preclude several kinds of optimizations that could be done within the scheduler (the proposal intentionally leaves some things unspecified to provide room for optimizations).
Secondly, try to remove the whole safe/unsafe/threadsafe business and make "safe" and "threadsafe" combinators too.
No ;-). I don't think it's at all possible to implement the distinction between unsafe and the others via a combinator, because unsafe is about things that just have to be in the C language RTS. In GHC, "unsafe" calls are compiled to just plain calls; "safe" and "threadsafe" calls first do some cleanup of RTS structures that makes sure that other (Haskell) threads or callbacks can run. After this cleanup is performed (function suspendThread()), no Haskell code can execute until resumeThread() is called. Therefore, it is impossible to do this using a combinator. So we're stuck with one optional specialid (not a keyword, "unsafe" is still a legal variable name) in foreign import declarations. Also, it's already widely used. Now what about the distinction between "safe" and "threadsafe"? _Maybe_ it's possible to implement "threadsafe" on top of "safe" and a few primitives (though I still doubt it). My position is to simply remove "safe" from the language altogether, because: *) It is poorly specified. There is no written document that specifies what exactly should block when, or what is required to block and what is just "allowed" to block. *) Most "informal" specifications that require "safe" calls to block other threads rely on specific details of present Haskell implementations. Future implementations could, and are allowed to, implement threading differently. Green threads are a feature of Haskell implementations, not a feature of the Haskell language. *) "safe" calls are dangerous. A "safe" call influences totally unrelated parts of a program in hard-to-predict ways. *) It seems to be relatively hard to actually achieve the performance benefit of "safe" calls (which was the original reason for having the distinction). If we remove "safe" from the language (and, consequently, rename "threadsafe" to "safe" and make it the default), we're left with only the distinction between "unsafe" and "safe" foreign imports (which we had from the start). If you don't specify anything, you get "safe" (a.k.a. threadsafe), which is the version that automatically "does the right thing". Unsafe foreign imports are provided as a performance optimization only (and it's not possible to achieve that using a combinator). There would be no unnecessary keywords or language extensions left.
Success, Daan.
Qapla', Wolfgang
participants (3)
-
Daan Leijen -
Simon Marlow -
Wolfgang Thaller