remains active and continues responding to its users. Although such tasks can be run
as processes, the efficiency and shared-state model of threads make them ideal for this
role. Moreover, since most GUI toolkits do not allow multiple threads to update the
GUI in parallel, updates are best restricted to the main thread.
Because only the main thread should generally update the display, GUI programs typ-
ically take the form of a main GUI thread and one or more long-running producer
threads—one for each long-running task being performed. To synchronize their points
of interface, all of the threads share data on a global queue: non-GUI threads post
results, and the GUI thread consumes them.
More specifically:
- The main thread handles all GUI updates and runs a timer-based loop that wakes
up periodically to check for new data on the queue to be displayed on-screen. In
Python’s tkinter toolkit, for instance, the widget after(msecs, func, *args) method
can be used to schedule queue-check events. Because such events are dispatched
by the GUI’s event processor, all GUI updates occur only in this main thread (and
often must, due to the lack of thread safety in GUI toolkits). - The child threads don’t do anything GUI-related. They just produce data and put
it on the queue to be picked up by the main thread. Alternatively, child threads
can place a callback function on the queue, to be picked up and run by the main
thread. It’s not generally sufficient to simply pass in a GUI update callback function
from the main thread to the child thread and run it from there; the function in
shared memory will still be executed in the child thread, and potentially in parallel
with other threads.
Since threads are much more responsive than a timer event loop in the GUI, this scheme
both avoids blocking the GUI (producer threads run in parallel with the GUI), and
avoids missing incoming events (producer threads run independent of the GUI event
loop and as fast as they can). The main GUI thread will display the queued results as
quickly as it can, in the context of a slower GUI event loop.
Also keep in mind that regardless of the thread safety of a GUI toolkit, threaded GUI
programs must still adhere to the principles of threaded programs in general—access
to shared resources may still need to be synchronized if it falls outside the scope of the
producer/consumer shared queue model. If spawned threads might also update an-
other shared state that is used by the main GUI thread, thread locks may also be required
to avoid operation overlap. For instance, spawned threads that download and cache
email probably cannot overlap with others that use or update the same cache. That is,
queues may not be enough; unless you can restrict threads’ work to queuing their
results, threaded GUIs still must address concurrent updates.
We’ll see how the threaded GUI model can be realized in code later in this book. For
more on this subject, see especially the discussion of threaded tkinter GUIs in
Threads | 209