Haskell, GTK and Multi-Threading
I have been working on an application in Haskell, using Gtk2Hs for the user interface. Now, you normally want a graphical user interface (GUI) to be responsive, so you avoid doing tasks that take a long time in the thread that handles the GUI. Instead, your main computation happens in other application threads, and all that happens in the GUI thread is updating of interface elements. It turns out that there are several issues that come up when you mix Haskell, GTK and multiple threads, which is why this post is here.
Part 1: The GTK event loop
Most programs that use Gtk2Hs first do all the GUI-related initialization, and then execute the mainGUI computation. This is actually a loop that processes GTK-related events until the user quits the program. Because of the way thread switching works in Haskell, a thread executing a loop like that will not let any other “lightweight” threads run. (Lightweight threads are threads created using the forkIO computation.) One frequently suggested way to solve this problem is to make the GTK event loop periodically yield to any other Haskell threads by adding the following to your GUI initialization code:
timeoutAddFull (yield >> return True) priorityDefaultIdle 100
Note that this is only an issue when all lightweight Haskell threads run on top of a single operating system thread, as in the single-threaded RTS of GHC. If Haskell threads are allowed to run on multiple OS threads, then yielding is not necessary.
Part 2: GTK thread safety
It turns out that that GTK (the C library wrapped by Gtk2Hs) is not thread-safe. What this means is that all modifications of GTK state must happen from a single OS thread, which also must be the same thread that is executing the GTK event loop. If all Haskell threads are run on top of a single OS thread, this is easy to ensure. However, if you use a system where lightweight threads may be mapped to different OS threads (such as in the multi-threaded RTS in GHC), care must be taken when accessing a GUI. Essentially, application threads need to put GUI modification events onto a queue, with the thread that runs the GTK event loop processing the events from that queue. Fortunately, Gtk2Hs comes with such a queue built-in; you can use the postGUI functions to give the event loop blocks of IO to execute.
Note that, by default, Gtk2Hs will produce a warning when running under the GHC multi-threaded RTS. To get rid of this, use unsafeInitGUIForThreadedRTS instead of the usual initGUI to perform GTK initialization. The “unsafe” part of the computation name signifies that you are aware of the requirement to only modify GTK state from the correct OS thread.
Part 3: Deconstructing the GTK event loop
There might come a time when you want to do something more complicated than the above solutions allow. In such a case, you can actually substitute the mainGUI event loop with your own. Gtk2Hs provides functions that will process a single event at a time off the GTK event queue. Put these in a loop, sprinkle with your custom logic (such as pulling events off multiple queues), and you’re done!
This was written with the GHC environment in mind. Other Haskell compilers and/or interpreters may differ in their implementations.