Concurrency (was: Re: [GUI] Re: events & callbacks)

Wolfgang Thaller
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

> 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.


> 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.