Concurrency (was: Re: [GUI] Re: events & callbacks)
Wolfgang Thaller
wolfgang.thaller@gmx.net
Thu, 13 Mar 2003 00:42:04 +0100
>> 2) Calls to the CGA can be made at any time, from any thread. The
>> implementation is responsible for assuring that the serialization
>> requirements of the backend toolkit are met.
>
> Unfortunately, you are not clear about what kind of thread you mean:
> OS thread or Haskell thread?
The CGA will be called from Haskell code, so I'm talking of Haskell
threads. However, an implementation may choose to use any OS thread it
likes to execute the Haskell code, so I'm also talking of OS threads.
> I therefore propose another rule:
>
> 2d) All haskell code is run in a single OS thread (the "GUI thread").
> Calls to the CGA
> can be made at any time from any Haskell thread (within the GUI
> thread).
My vote is a big NO: This means restricting what a Haskell runtime
system is allowed to do.
> Note that:
> - We can run many haskell threads within one OS thread. Furthermore,
> the interaction
> between Haskell threads is well understood and light weight.
But there is no standard way to manage the correspondence between
haskell threads and OS threads. Relying on the fact that most current
implementations use one OS thread for all haskell threads is just
asking for trouble.
> - The serialization requirements of the backend toolkit are
> automatically met since
> all these haskell threads run in the same OS thread.
... unless the toolkit uses some global state variable (the "current
graphics port" in carbon; drawing to one window from multiple _haskell_
threads simultanously would probably be a problem for Win32, too, as
Drawing Context state may be modified in the process).
> - All current Haskell systems can easily support this model -- in
> contrast, no haskell
> system (except the LVM in Helium :-) supports multiple OS threads to
> my knowledge.
The "threaded RTS" is disabled by default in GHC 5.04, because it
wasn't well tested and contained several bugs. I fixed a whole lot of
bugs for the GHC 5.05 CVS version, and therefore I hope htat GHC 5.06
will fully support using multiple OS threads.
> - We can implement all advanced event models on top of this model
> using concurrency.
> - We most surely do *not* want foreign C calls to be run in a
> different OS thread
> and don't want the "threadsafe" keyword here. (I am opposed to such
> extension --
> complexity without reason - run your OS threads from C yourself!)
We (I have to admit, that means: I) most surely *do* want the
"threadsafe" keyword here. After all, it is a feature that I have been
taking for granted before I switched from C++ to Hasell. Having to
think about an implementation detail of Haskell where things work "just
right" when you use C++ is scary and definitely the wrong way to go.
That's why threadsafe calls are the future. (On the other hand,the
behaviour of "safe" calls has never been specified accurately, is
difficult to implement in implementations that properly support
multiple OS threads, and will probably have to go, unless somebody
comes up with a meaningful and implementable specification on
ffi@haskell.org).
> As you explained, a callback can use forkIO to achieve concurrent
> callbacks
> for example. However a more "real world" reason for having concurrency
> would
> be that a callback needs to do a lot of processing. As callbacks are
> run
> sequentially, the entire application will not react to events while the
> callback does its work. Therefore, a callback that needs to do a lot
> of processing
> should spawn a Haskell thread to do the processing (a worker thread)
> and
> return as soon as possible in order to stay reactive.
Agreed.
> Now, the big issue here is how to keep those Haskell threads running!
> [...]
With threadsafe calls, that problem disappears. There'll just be normal
synchronization issues like in every other language.
> When a callback returns that has just forked another Haskell thread,
> the eventloop will make an OS call to wait for the next event. Since
> we are running
> in a single OS thread, this will "disable" the Haskell scheduler and
> the worker thread
> will not run at all until another event happens!
>
> The question is how to solve this. Alastair Reid used the "yield"
> primitive to wait until
> all Haskell threads were done (or called yield themselves), but that
> wouldn't solve our particular problem as we want to stay reactive
> *and* have a lot of processing to do.
>
> Now, I think that we should use "idle time" events to solve this. It
> would stay nicely in
> our simple concurrency model and also keep the whole GUI desktop
> reactive (do we remember
> the old Winhugs bug :-). Basically, when an "idle" time event happens
> we call a callback
> that just sleeps for, say, 0.1 seconds, giving the Haskell scheduler
> time to run other
> haskell threads. It would be good of course when we have some hooks to
> determine whether
> there are any haskell threads waiting to be scheduled so we can avoid
> calling that handler
> when not necessary. We don't necessarily have to use real "idle"
> events: if there are Haskell
> treads waiting to be scheduled, we can also peek at the event queue,
> and when no new events
> have arrived, call the idle callback. When all haskell threads are
> done, we wait again.
> Note that this schedule also give more priority to new events, keeping
> the whole application
> highly reactive.
I don't like that. Having those "hooks" into the runtime system sounds
like much more unnecessary (and implementation-specific) complexity
than the "threadsafe" extension. Not having those hooks would waste
some CPU time.
It should of course be possible to implement the CGA on
single-OS-thread haskell implementations (using the "idle time" scheme
you proposed above). But we should write the specification in a way
that allows taking advantage of "threadsafe".
It would be a big mistake to write the specification in a way that is
incompatible with a planned feature of the next version of GHC.
Cheers,
Wolfgang