Why Are Most UI Frameworks Single Threaded

Why must/should UI frameworks be single threaded?

TL;DR

It is a simple way to force sequencing to occur in an activity that is going to ultimately be in sequence anyway (the screen draw X times per second, in order).

Discussion

Handling long-held resources which have a single identity within a system is typically done by representing them with a single thread, process, "object" or whatever else represents an atomic unit with regard to concurrency in a given language. Back in the non-emptive, negligent-kernel, non-timeshared, One True Thread days this was managed manually by polling/cycling or writing your own scheduling system. In such a system you still had a 1::1 mapping between function/object/thingy and singular resources (or you went mad before 8th grade).

This is the same approach used with handling network sockets, or any other long-lived resource. The GUI itself is but one of many such resources a typical program manages, and typically long-lived resources are places where the ordering of events matters.

For example, in a chat program you would usually not write a single thread. You would have a GUI thread, a network thread, and maybe some other thread that deals with logging resources or whatever. It is not uncommon for a typical system to be so fast that its easier to just put the logging and input into the same thread that makes GUI updates, but this is not always the case. In all cases, though, each category of resources is most easily reasoned about by granting them a single thread, and that means one thread for the network, one thread for the GUI, and however many other threads are necessary for long-lived operations or resources to be managed without blocking the others.

To make life easier its common to not share data directly among these threads as much as possible. Queues are much easier to reason about than resource locks and can guarantee sequencing. Most GUI libraries either queue events to be handled (so they can be evaluated in order) or commit data changes required by events immediately, but get a lock on the state of the GUI prior to each pass of the repaint loop. It doesn't matter what happened before, the only thing that matters when painting the screen is the state of the world right then. This is slightly different than the typical network case where all the data needs to be sent in order and forgetting about some of it is not an option.

So GUI frameworks are not multi-threaded, per se, it is the GUI loop that needs to be a single thread to sanely manage that single long-held resource. Programming examples, typically being trivial by nature, are often single-threaded with all the program logic running in the same process/thread as the GUI loop, but this is not typical in more complex programs.

To sum up

Because scheduling is hard, shared data management is even harder, and a single resource can only be accessed serially anyway, a single thread used to represent each long-held resource and each long-running procedure is a typical way to structure code. GUIs are only one resource among several that a typical program will manage. So "GUI programs" are by no means single-threaded, but GUI libraries typically are.

In trivial programs there is no realized penalty to putting other program logic in the GUI thread, but this approach falls apart when significant loads are experienced or resource management requires either a lot of blocking or polling, which is why you will often see event queue, signal-slot message abstractions or other approaches to multi-threading/processing mentioned in the dusty corners of nearly any GUI library (and here I'm including game libraries -- while game libs typically expect that you want to essentially build your own widgets around your own UI concept, the basic principles are very similar, just a bit lower-level).

[As an aside, I've been doing a lot of Qt/C++ and Wx/Erlang lately. The Qt docs do a good job of explaining approaches to multi-threading, the role of the GUI loop, and where Qt's signal/slot approach fits into the abstraction (so you don't have to think about concurrency/locking/sequencing/scheduling/etc very much). Erlang is inherently concurrent, but wx itself is typically started as a single OS process that manages a GUI update loop and Erlang posts update events to it as messages, and GUI events are sent to the Erlang side as messages -- thus permitting normal Erlang concurrent coding, but providing a single point of GUI event sequencing so that wx can do its GUI update looping thing.]

Why are most UI frameworks single threaded?

What made the framework designers chose one thread model over the other?

From the horse's mouth:

AWT was initially exposed as a normal
multi-threaded Java library. But as
the Java team looked at the experience
with AWT and with the deadlocks and
races that people had encountered, we
began to realize that we were making a
promise we couldn't keep.

This analysis culminated in one of the
design reviews for Swing in 1997, when
we reviewed the state of play in AWT,
and the overall industry experience,
and we accepted the Swing team's
recommendation that Swing should
support only very limited
multi-threading.

(Read the whole article, it explains the decision in great detail and states that the exact same problems and eventual move to a single-threaded model had even occured earlier at Xerox PARC - the place where almost everything we consider bleeding edge modern in CS was invented 30 years ago)

Wouldn't multiple threaded UI model
potentially give you more performance
albeit at the cost of more complexity?

Absolutely not, because drawing the GUI and processing user actions (which is everything the UI thread needs to do) is not going to be the bottleneck in any sane 2D application.

Why is a single threaded model used to update the UI as main thread?

The short answer is, it's the only reasonable way to ensure that the display is not corrupted.

The long answer is that allowing multiple threads to update the UI results in deadlocks, race conditions, and all sorts of trouble. This was the painful lesson taught by Java's AWT (among other UI systems) that allows multiple threads to touch the UI. See, for instance, Multithreaded toolkits: A failed dream?. That post refers (via dead links) to Why Threads Are A Bad Idea and Threadaches.

Should all event-driven frameworks be single-threaded?

I would have to say no, with a caveat. Event driven frameworks that revolve around shared state, such as a UI, should be single threaded. Event driven frameworks that revolve around notifications, such mechanical monitoring(e.g. letting you know when the pressure in a pipe is too high), could be single threaded, but might be more appropriate to be multi threaded.

It is certainly possible to build a multi threaded UI framework, and I have done so myself. In the end I converted it to be single threaded. Part of the reason does fall under what Charlie said about "it's too hard". The problem was that with a multi-threaded UI framework, it wasn't just me that had to deal with the threading, but anyone that used the UI. The core was certainly thread safe, but then anyone that wrote a control had to make that thread safe as well. Nevermind that when making multiple changes to the UI, you had to notify the core that you were doing so so you didn't get partial updates. Since a user is generally a pretty slow thing, any performance gain was really negligible, and could be worked around with an asynchronous call if necessary for specific cases. Single threading is actually the appropriate model here.

On the other hand, in a model where there isn't(or isn't much) shared state, a multi threaded model makes eminent sense. There's no reason for one event(the reactor is on fire) to be delayed for the 30 seconds it takes for your query(Bob the operator just clocked out) to timeout because some yahoo in operations left the punch_card table locked.

Best practice regarding number of threads in GUI applications

I've seen the same thing. Ideally you should perform any operation that is going to take longer then a few hundred ms in a background thread. Anything sorter than 100ms and a human probably wont notice the difference.

A lot of GUI programmers I've worked with in the past are scared of threads because they are "hard". In some GUI frameworks such as the Delphi VCL there are warnings about using the VCL from multiple threads, and this tends to scare some people (others take it as a challenge ;) )

One interesting example of multi-threaded GUI coding is the BeOS API. Every window in an application gets its own thread. From my experience this made BeOS apps feel more responsive, but it did make programming things a little more tricky. Fortunately since BeOS was designed to be multi-threaded by default there was a lot of stuff in the API to make things easier than on some other OSs I've used.

Why is UI drawn on main thread?

"Because multithreaded drawing quickly becomes too complex/confusing" is only half the answer.

The other major impediment to multithreaded UI management is event handling. Mixing processing of events into concurrent drawing, specifically. You would have to somehow intermingle drawing with the chaos that is a monkey bashing on screen/keyboard/mouse while effectively maintaing a notion of transactional integrity.

Already, this is hard without concurrency.

Are single-threaded applications a dead technology?

Since threading always adds extra complexity to applications, I believe that single threaded applications will always have their place.

Not even when single core processors are completely obsolete will single threaded programming be gone.

Dual cores, especially in the consumer market, are great for multitasking. If every app takes every processor core it possibly can we will run into the same problems we had with single core processors.

I say don't go nuts and start mulithreading everything. Keep it in one thread unless there is a good reason not too.

Why is it wrong to access GUI elements from another thread?

Of those you listed, some may be wrappers around existing mechanisms, so you have to answer the question indirectly via the underlying GUI framework. In case of multi-platform GUI frameworks like e.g. Qt, you will also have the lowest-common denominator that determines what is possible and what isn't.

Now, why is access to the GUI not thread-safe? In the cases where I'm most familiar with (win32 and X11), accesses are often performed indirectly by sending requests and sometimes waiting for the according answer. This usually works in an atomic way, even across process boundaries, so that is not directly cause of the problem. However, if you do so from multiple threads, the worst that can happen is that data is modified in an uncoordinated way. For example, if you read, modify and write the same widget from two threads, these operations might be interleaved, so that only one thread's modifications will actually be applied.

There are other reasons for not supporting cross-thread access:

  • In win32, the queue with the messages is thread-local, which means that only the thread that created a window will actually find and be able to handle messages for that window. I guess this a legacy from times where processes were single-threaded and the message queue was simply a global. Making it thread-local is the same approach as the one used for making errno thread-safe.
  • Another reason is that support objects are created inside a process that represent some GUI element. For example, the MFC (on top of win32) use a map from the OS' widget handle to a C++ object representing that object. That map is stored in thread-local storage (which follows the thread-local message queue) and the access to the C++ objects is not guarded by a mutex. Accessing these objects from different threads is bad, not because they represent GUI objects but because they are not synchronized, simple as that.
  • If you think about modifying the structure of a widget tree (like e.g. the DOM tree in a browser), you either have very detailed knowledge of what other parts of the application are doing or you need to lock access to the whole tree before every operation just to be safe. Needless to say, this effectively prevents any parallel operations, so you can also take the next step and require all operations to come from one thread and thus save the whole multithreading overhead.

That said, I believe that Qt and C# (and probably others) actually do support some cross-thread operations. They will work some (more or less obscure) magic that forwards the calls to the GUI thread and forwards the results back to the calling thread again. In other words, they try to make the necessary inter-thread communication more convenient for the programmer, while retaining the efficiency and simplicity of the single-threaded GUI. This is not restricted to GUI handling though but rather a general approach, only that it is especially important for the GUI.

Why can only the UI thread in Android update the UI?

Documentation states that Android UI toolkit is not thread-safe. Thus, single thread model ensures UI is not modified by different threads at the same time.

Why the same thread must be used?

Most windowing systems have fairly specific rules about the interaction between threads and the code that (directly) manipulates a window. For most typical cases, this is handled fairly transparently by the fact that the "outside world" sends messages, and the code that handles those messages and directly manipulates the target window runs in the (one) right thread.

I'd guess we're seeing a manifestation of the same limitations here. The difference is that that OpenGL and Swing EDT expose the functionality in terms of function calls instead of messages to send. Those functions directly manipulate the underlying window in ways that a typical windowing system requires happen only from the right thread--therefore, you'd better only call those functions from the right thread.

As far as enforcement goes: at least in most systems I've seen, the "enforcement" consists of your application crashing when you do the wrong thing. If you're lucky, you might get a nice, helpful message from the OS saying you called function X from the wrong thread (though the function X to which it refers, may well be one you never called directly at all, from any thread). In the more common case, you'll get an error like segmentation fault attempting to write address 0x12345678, with no indication of what you did wrong to trigger that at all.

As to why it's done: as noted above, it's more or less mandated by underlying windowing systems. If you want to go a step further and ask why they mandate it, I'd guess it's primarily a matter of simplicity and speed. Requiring that all direct interaction with a window go through a single thread avoids dealing with concurrency everywhere. Adding all the mutexes (and such) necessary to allow multiple threads to work with it concurrently would lead to much slower development, and (probably) quite a bit slower execution as well.



Related Topics



Leave a reply



Submit