Multithreaded UI code may be just as hard as multithreaded non-UI code, but the consequences are different

Commenter Tim Smith claims that the problems with multithreaded UI code are not significantly more than plain multithreaded code. While that may be true on a theoretical level, the situations are quite different in practice.

Regardless of whether your multithreaded code does UI or not, you have to deal with race conditions, synchronization, cache coherency, priority inversion, all that mulitthreaded stuff.

The difference is that multithreaded problems with non-UI code are often rare, relying on race conditions and other timing issues. As a result, you can often get away with a multithreaded bug, because it may shows up in practice only rarely, if ever. (On the other hand, when it does show up, it's often impossible to diagnose.)

If you mess up multithreaded UI code, the most common effect is a hang. The nice thing about this is that it's easier to diagnose because everything has stopped and you can try to figure out who is waiting for what. On the other hand, the problems also occur with much more frequency.

So it's true that the problems are the same, but the way they manifest themselves are very different.

Comments (22)
  1. Biff Turkle says:

    Having a bug show up early and often is a GOOD THING.  I don't like bugs that only show up 6 months down the road.  In Production.

  2. sukru says:

    @Biff Turkle, I wholly agree. It's better to get early warning, and that's why we have strict compilers, and unit tests, and even coverage tests for those unit tests.

  3. Joshua Ganes says:

    All the more reason to avoid multi-threaded code like the plague. Indeed, sometimes it is unavoidable. This makes me sad. With the speed of today's computers, multi-processed solutions can sometimes be a decent compromise.

  4. Peter da Silva says:

    Also, for most programmers UI code has been about the only time they will have to deal with multithreaded code. Even if the framework is ostensibly single-threaded, you often find callbacks or other interrupt-like mechanisms being used to try and improve performance over a classical single-event-loop model, because keeping an event-loop working fast is hard.

    I really think the solution is to teach people how to write fast single-loop code, use single-loop frameworks, and only going to multithreaded code when that fails.

  5. poday says:

    Josh, I have to disagree.  For good or ill computers are moving to more and more cores.  It makes me sad when I see one core pegged while the other 7 cores are idle.  Multi-threading is the future and our only option is to start trying to wrangle it.  Our tools and languages will/should go a long way in simplifying the problem (say to warn about deadlock) but they won't be able to handle architectural problems.  Beginning to understand the issues now will mean a less confusing world later.

  6. Kyle says:


    In principle I agree, however it's important to note that the way threading had been handled historically generally involved mutual exclusion.  Such cross-communications don't work in GPU-type environments, which has forced new threading models.  So yes, threading will become more pervasive, but also new algorithmic approaches will become more pervasive as well, including lock and wait free approaches and also software transaction memory approaches.  These become important not only for GPU computation, but also for large scale computation as well, since they help to guarantee system-wide progress.

  7. Joshua Ganes says:

    @poday – Everything is contextual. If you have a program with long-running, CPU-bound processing, then multi-threading or multi-processing is definitely the way to go to increase efficiency. If you have a program with lots of user interaction and occasional processing, then you want to streamline the user interface as much as possible. It's a lot easier to prevent race conditions when there is no race to begin with.

  8. Gabe says:

    Of course there are different degrees of multi-threaded-ness in UI code. Perhaps the easiest kind is just where a process (like Explorer) has different independent windows that each is owned by its own thread. Since each is independent, there is not going to be many problems with the threads interacting and causing problems.

    The most common kind is probably where you have one thread managing the UI and another thread doing work and updating the UI (e.g. incrementing a progress bar). Although it requires care, this type of multithreading is not too difficult if you have experience with writing multithreaded code.

    I'd say the worst is when you have a single window that just gets random stuff done to it by random (e.g. threadpool) threads. Perhaps one thread selects a brush into a DC and another thread draws with it. This should be avoided at all costs because the hassles just aren't worth it.

  9. Baldwin says:

    And how exactly are you planning to keep the UI responsive without using multi-threading?

  10. Joshua Ganes says:

    To clarify my previous posts: I advocate minimizing multi-threaded logic without crippling your application. As I said previously, "sometimes it is unavoidable." In these cases, you should attempt to keep the interaction between threads to a minimum to isolate the code segments with potential for multi-threading bugs. I don't know about the rest of you, but my brain is feeble when trying to decipher multi-threaded logic. Choose the simplest approach to get the job done. In certain contexts, multi-threading will be the simplest approach to meet the requirements of flexibility and speed.

  11. Cheong says:

    @Baldwin: In VB6 days when we don't have multithread, we add a lot of DoEvents at code fragments to ensure the message queue have time to do the tasks.

    Yes, it's slower that way, but no problem in keeping the UI response as long as there aren't some kind of blocking operation (like file I/Os) take a long time to finish.

  12. Jeff says:

    poday: Most of the time, programs with long amounts of high CPU usage aren't doing legitimate computation; they're doing something easy but in a really stupidly implemented way. I'm *glad* the kinds of programmers who write that stuff haven't learned about threads yet, or "serial bubble sort ties up 1 core for 10 minutes, you can still do stuff with the other 7" would become "parallel bubble sort, with lots of synchronization overhead, ties up all 8 cores for 20 minutes".

  13. steveg says:

    @Joshua Games, et al: My first response when someone says "Woo! We'll make it multi-threaded", is always "Do you *need* to do that? Really?" Which is a specific version of Keep It Simple, Stupid.

    As Raymond says multi-threading programming is hard, why introduce stress into your life if there's an alternative.

  14. Neil says:

    @Jeff: This is what the "Set Affinity" process option is for ;-)

  15. Joseph Koss says:

    @Jeff: Be honest. The programmers using bubble sort for large data sets arent going to write a parallel version. They might spawn 10 different sorts simultaneously, but they will never ever be able to parallelize bubble.

    To be quite honest, I'm not entirely sure how bubble could be parallelized anyways. Seems to me that it would require a set of merges and at that point you've essentially got a merge sort, a top-notch easily parallelized sort that is very hard to beat in practice

    @Cheong: C# and VB.NET are both replacing the need for DoEvents even for single-threaded constructions. There is currently a preview of things to come called 'Async' .. a search for 'async ctp' (community technology preview) will bring you right to it with a download link. This is a language extension for both and not part of the framework, so it requires compiler support and probably wont be found in other .NET languages. It also applies to multi-threaded programming and I must say that it makes that quite a bit more bearable (eliminates callback spaghetti)

  16. Just Another Code Monkey says:

    Problems with multithreaded code (UI or not) are kinda fun to debug. But dealing with COM in a multithreaded environment is some pain. I wonder why there aren't (any) articles related to COM here. ;)

  17. Joshua says:

    I don't know but I was glad to remove our last in-house COM component. Our only remaining use of COM is interop to MS-Office as there is no other API for this.

  18. Ben Voigt says:

    @Cheong: "blocking operations (like file I/Os)"… I/O (except paging) aren't blocking operations.  The very first thing you do when trying to stay single-threaded is learn the overlapped I/O model.

  19. Cheong says:

    @Just Another Code Monkey: I think Raymond has covered some aspect here… as long as you're writing COM with UI, use STA model, don't use MTA.

    For others replying me, notice what I said applies to VB6 days, when most programmers does not write program with concept of multithreading in mind. DoEvents() is the commonly used way to keep UI responsive those days.

  20. Worf says:

    @Ben Voigt: True, async I/O allows you to single thread blocking operations. However, depending on your processing model, it may be easier to maintain and simpler to understand id you multithread and let the I/O thread block and process "naturally". Otherwise you can fall prey to various caveats.

    And nevermind those in the end that go through the motions only to end up with multithreading anyhow because they started having to handle all the I/O in another thread.

    Sometimes it's simpler to do the file I/O-process loop in another thread rather than start I/O, do other eventing, get notification, handle I/O and file data, process.

    And in some OSes, the async I/O is effectively an interrupt with the program state in an unknown area. Synchronizing is a pain, and elaborate schemes cooked up to pass data around and maintain system state, etc.

    Spaghetti code can result.

  21. Gabe says:

    I thought we already covered this. You can't call CreateFile in async mode (because it doesn't take a file handle), hence you always have to call it in a separate thread to maintain a responsive UI. In fact, only a few file operations are available in async mode (read/write, lock/unlock, etc.), while all others are synchronous. Most anything you want to do concerning metadata is synchronous.

  22. Random832 says:

    "To be quite honest, I'm not entirely sure how bubble could be parallelized anyways."

    start eight copies of the algorithm, with lots of locking to make sure they don't step on each other.

Comments are closed.

Skip to main content