views:

914

answers:

13

In a previous question, I made a bit of a faux pas. You see, I'd been reading about threads and had got the impression that they were the tastiest things since kiwi jello.

Imagine my confusion then, when I read stuff like this:

[T]hreads are A Very Bad Thing. Or, at least, explicit management of threads is a bad thing

and

Updating the UI across threads is usually a sign that you are abusing threads.

Since I kill a puppy every time something confuses me, consider this your chance get your karma back in the black...

How should I be using thread?

+8  A: 

Explicit management of threads is not intrinsically a bad thing, but it's frought with dangers and shouldn't be done unless absolutely necessary.

Saying threads are absolutely a good thing would be like saying a propeller is absolutely a good thing: propellers work great on airplanes (when jet engines aren't a better alternative), but wouldn't be a good idea on a car.

MusiGenesis
For what its worth, I think propellers on a car would be great.
Tom Wright
http://www.dself.dsl.pipex.com/museum/transport/helica/helica.htm
MusiGenesis
+1  A: 

Threads are a very good thing, I think. But, working with them is very hard and needs a lot of knowledge and training. The main problem is when we want to access shared resources from two other threads which can cause undesirable effects.

Consider classic example: you have a two threads which get some items from a shared list and after doing something they remove the item from the list.

The thread method that is called periodically could look like this:

void Thread()
{
   if (list.Count > 0)
   {
      /// Do stuff
      list.RemoveAt(0);
   }
}

Remember that the threads, in theory, can switch at any line of your code that is not synchronized. So if the list contains only one item, one thread could pass the list.Count condition, just before list.Remove the threads switch and another thread passes the list.Count (list still contains one item). Now the first thread continues to list.Remove and after that second thread continues to list.Remove, but the last item already has been removed by the first thread, so the second one crashes. That's why it would have to be synchronized using lock statement, so that there can't be a situation where two threads are inside the if statement.

So that is the reason why UI which is not synchronized must always run in a single thread and no other thread should interfere with UI.

In previous versions of .NET if you wanted to update UI in another thread, you would have to synchronize using Invoke methods, but as it was hard enough to implement, new versions of .NET come with BackgroundWorker class which simplifies a thing by wrapping all the stuff and letting you do the asynchronous stuff in a DoWork event and updating UI in ProgressChanged event.

František Žiačik
+3  A: 

I think the first statement is best explained as such: with the many advanced APIs now available, manually writing your own thread code is almost never necessary. The new APIs are a lot easier to use, and a lot harder to mess up!. Whereas, with the old-style threading, you have to be quite good to not mess up. The old-style APIs (Thread et. al.) are still available, but the new APIs (Task Parallel Library, Parallel LINQ, and Reactive Extensions) are the way of the future.

The second statement is from more of a design perspective, IMO. In a design that has a clean separation of concerns, a background task should not really be reaching directly into the UI to report updates. There should be some separation there, using a pattern like MVVM or MVC.

Stephen Cleary
I would not say that it's now *a lot* harder to mess up. Those libraries simplify thread creation, but still you have to take care of access to shared data.
el.pescado
Both PLINQ and TPL contain constructs that synchronize all the data sharing for you. It's not *always* possible to use these, but they do simplify both thread creation and access to shared data.
Stephen Cleary
Not to mention the new data structures designed specifically for concurrency. I think it's indeed much more difficult to mess up, assuming you learn all of the TPL and PLINQ.
drharris
No, the second statement is received wisdom of decades of experience in building UIs: getting an multithreaded UI to work correctly is even harder than multithreaded programming in general. That's why the model of a single UI thread that interacts with others via an event queue has become almost ubiquitous.
Michael Borgwardt
+5  A: 

Unless you are on the level of being able to write a fully-fledged kernel scheduler, you will get explicit thread management always wrong.

Threads can be the most awesome thing since hot chocolate, but parallel programming is incredibly complex. However, if you design your threads to be independent then you can't shoot yourself in the foot.

As fore rule of the thumb, if a problem is decomposed into threads, they should be as independent as possible, with as few but well defined shared resources as possible, with the most minimalistic management concept.

sibidiba
+2  A: 

Many advanced GUI Applications usually consist of two threads, one for the UI, one (or sometimes more) for Processing of data (copying files, making heavy calculations, loading data from a database, etc).

The processing threads shouldn't update the UI directly, the UI should be a black box to them (check Wikipedia for Encapsulation).
They just say "I'm done processing" or "I completed task 7 of 9" and call an Event or other callback method. The UI subscribes to the event, checks what has changed and updates the UI accordingly.

If you update the UI from the Processing Thread you won't be able to reuse your code and you will have bigger problems if you want to change parts of your code.

dbemerlin
It's worth mentioning that in some of the most prolific UI APIs out there (e.g., Windows), it's also _illegal_ to update the UI from any thread that doesn't own the UI.
Greg D
+3  A: 

I would start by questioning this perception:

I'd been reading about threads and had got the impression that they were the tastiest things since kiwi jello.

Don’t get me wrong – threads are a very versatile tool – but this degree of enthusiasm seems weird. In particular, it indicates that you might be using threads in a lot of situations where they simply don’t make sense (but then again, I might just mistake your enthusiasm).

As others have indicated, thread handling is additionally quite complex and complicated. Wrappers for threads exist and only in rare occasions do they have to be handled explicitly. For most applications, threads can be implied.

For example, if you just want to push a computation to the background while leaving the GUI responsive, a better solution is often to either use callback (that makes it seem as though the computation is done in the background while really being executed on the same thread), or by using a convenience wrapper such as the BackgroundWorker that takes and hides all the explicit thread handling.

A last thing, creating a thread is actually very expensive. Using a thread pool mitigates this cost because here, the runtime creates a number of threads that are subsequently reused. When people say that explicit management of threads is bad, this is all they might be referring to.

Konrad Rudolph
+2  A: 

I think you should experiement as much as possible with Threads and get to know the benefits and pitfalls of using them. Only by experimentation and usage will your understanding of them grow. Read as much as you can on the subject.

When it comes to C# and the userinterface (which is single threaded and you can only modify userinterface elements on code executed on the UI thread). I use the following utility to keep myself sane and sleep soundly at night.

 public static class UIThreadSafe {

     public static void Perform(Control c, MethodInvoker inv) {
            if(c == null)
                return;
            if(c.InvokeRequired) {
                c.Invoke(inv, null);
            }
            else {
                inv();
            }
      }
  }

You can use this in any thread that needs to change a UI element, like thus:

UIThreadSafe.Perform(myForm, delegate() {
     myForm.Title = "I Love Threads!";
});
Adrian Regan
I find it much easier to express this as a `SafeInvoke()` extension method on `Control`s. That way you don't have to remember some utility method every time, but it's right there in the flesh.
drharris
+4  A: 

You cannot appreciate what kind of problems threading can cause unless you've debugged a three-way deadlock. Or spent a month chasing a race condition that happens only once a day. So, go ahead and jump in with both feet and make all the kind of mistakes you need to make to learn to fear the Beast and what to do to stay out of trouble.

Hans Passant
Good point. I've read about threadpool starvation, which sounds like fun...
Tom Wright
+1  A: 

A huge reason to try to keep the UI thread and the processing thread as independent as possible is that if the UI thread freezes, the user will notice and be unhappy. Having the UI thread be blazing fast is important. If you start moving UI stuff out of the UI thread or moving processing stuff into the UI thread, you run a higher risk of having your application become unresponsive.

Also, a lot of the framework code is deliberately written with the expectation that you will separate the UI and processing; programs will just work better when you separate the two out, and will hit errors and problems when you don't. I don't recall any specifics issues that I encountered as a result of this, though I have vague recollections of in the past trying to set certain properties of stuff the UI was responsible for outside of the UI and having the code refuse to work; I don't recall whether it didn't compile or it threw an exception.

Brian
I've reached my voting limit for the day (guess I'm too enthusiastic), but I found this useful and it will get a vote tomorrow if I remember!
Tom Wright
+67  A: 

Enthusiam for learning about threading is great; don't get me wrong. Enthusiasm for using lots of threads, by contrast, is symptomatic of what I call Thread Happiness Disease.

Developers who have just learned about the power of threads start asking questions like "how many threads can I possible create in one program?" This is rather like an English major asking "how many words can I use in a sentence?" Typical advice for writers is to keep your sentences short and to the point, rather than trying to cram as many words and ideas into one sentence as possible. Threads are the same way; the right question is not "how many can I get away with creating?" but rather "how can I write this program so that the number of threads is the minimum necessary to get the job done?"

Threads solve a lot of problems, it's true, but they also introduce huge problems:

  • Performance analysis of multi-threaded programs is often extremely difficult and deeply counterintuitive. I've seen real-world examples in heavily multi-threaded programs in which making a function faster without slowing down any other function or using more memory makes the total throughput of the system smaller. Why? Because threads are often like streets downtown. Imagine taking every street and magically making it shorter without re-timing the traffic lights. Would traffic jams get better, or worse? Writing faster functions in multi-threaded programs drives the processors towards congestion faster.

What you want is for threads to be like interstate highways: no traffic lights, highly parallel, intersecting at a small number of very well-defined, carefully engineered points. That is very hard to do. Most heavily multi-threaded programs are more like dense urban cores with stoplights everywhere.

  • Writing your own custom management of threads is insanely difficult to get right. The reason is because when you are writing a regular single-threaded program in a well-designed program, the amount of "global state" you have to reason about is typically small. Ideally you write objects that have well-defined boundaries, and that do not care about the control flow that invokes their members. You want to invoke an object in a loop, or a switch, or whatever, you go right ahead.

Multi-threaded programs with custom thread management require global understanding of everything that a thread is going to do that could possibly affect data that is visible from another thread. You pretty much have to have the entire program in your head, and understand all the possible ways that two threads could be interacting in order to get it right and prevent deadlocks or data corruption. That is a large cost to pay, and highly prone to bugs.

  • Essentially, threads make your methods lie. Let me give you an example. Suppose you have:

    if (!queue.IsEmpty) queue.RemoveWorkItem().Execute();

Is that code correct? If it is single threaded, probably. If it is multi-threaded, what is stopping another thread from removing the last remaining item after the call to IsEmpty is executed? Nothing, that's what. This code, which locally looks just fine, is a bomb waiting to go off in a multi-threaded program. Basically that code is actually:

 if (queue.WasNotEmptyAtSomePointInThePast) ...

which obviously is pretty useless.

So suppose you decide to fix the problem by locking the queue. Is this right?

lock(queue) {if (!queue.IsEmpty) queue.RemoveWorkItem().Execute(); }

That's not right either, necessarily. Suppose the execution causes code to run which waits on a resource currently locked by another thread, but that thread is waiting on the lock for queue - what happens? Both threads wait forever. Putting a lock around a hunk of code requires you to know everything that code could possibly do with any shared resource, so that you can work out whether there will be any deadlocks. Again, that is an extremely heavy burden to put on someone writing what ought to be very simple code. (The right thing to do here is probably to extract the work item in the lock and then execute it outside the lock. But... what if the items are in a queue because they have to be executed in a particular order? Now that code is wrong too because other threads can then execute later jobs first.)

  • It gets worse. The C# language spec guarantees that a single-threaded program will have observable behaviour that is exactly as the program is specified. That is, if you have something like "if (M(ref x)) b = 10;" then you know that the code generated will behave as though x is accessed by M before b is written. Now, the compiler, jitter and CPU are all free to optimize that. If one of them can determine that M is going to be true and if we know that on this thread, the value of b is not read after the call to M, then b can be assigned before x is accessed. All that is guaranteed is that the single-threaded program seems to work like it was written.

Multi-threaded programs do not make that guarantee. If you are examining b and x on a different thread while this one is running then you can see b change before x is accessed, if that optimization is performed. Reads and writes can logically be moved forwards and backwards in time with respect to each other in single threaded programs, and those moves can be observed in multi-threaded programs.

This means that in order to write multi-threaded programs where there is a dependency in the logic on things being observed to happen in the same order as the code is actually written, you have to have a detailed understanding of the "memory model" of the language and the runtime. You have to know precisely what guarantees are made about how accesses can move around in time. And you cannot simply test on your x86 box and hope for the best; the x86 chips have pretty conservative optimizations compared to some other chips out there.

That's just a brief overview of just a few of the problems you run into when writing your own multithreaded logic. There are plenty more. So, some advice:

  • Do learn about threading.
  • Do not attempt to write your own thread management in production code.
  • Use higher-level libraries written by experts to solve problems with threads. If you have a bunch of work that needs to be done in the background and want to farm it out to worker threads, use a thread pool rather than writing your own thread creation logic. If you have a problem that is amenable to solution by multiple processors at once, use the Task Parallel Library. If you want to lazily initialize a resource, use the lazy initialization class rather than trying to write lock free code yourself.
  • Avoid shared state.
  • If you can't avoid shared state, share immutable state.
  • If you have to share mutable state, prefer using locks to lock-free techniques.
Eric Lippert
Excellent overview, Eric! +1
Stephen Cleary
I have to admit that I +1ed this answer *before* reading it. Luckily, I can now say that it was actually that good.
Konrad Rudolph
Great analogy (threading : streets and traffic lights).
MusiGenesis
"It gets worse..." Using the `volatile` turns this optimization off for specific variables.
Brian
@Brian: volatile is not a panacea; you can't just go making a bunch of stuff volatile and hope for the best. And it introduces new performance costs as well. To use volatile correctly, you have to have a detailed understanding of what exactly a volatile read and a volatile write are in the CLR memory model. Which, again, is a high burden to put on people.
Eric Lippert
@Konrad Rudolph - it's OK to move upvotes before answer reads as long as nobody else observes the temporary inconsistant state caused by this optimization.
Jeffrey L Whitledge
@Jeffrey: Hah! -----------------
Eric Lippert
I just learned so much...
Sean O'Hollaren
I knew it was you as soon as i read the words "Thread Happiness Disease" :)
RCIX
*Standing Ovation*
Pierreten
Definately one of the best reads on SO so far. Great job!
Nubsis
Very informative answer, @Eric. Though I'm curious why you recommend *against* lock-free techniques (I take it that this includes lock-free data structures) in the very last point you made? -- I thought these were supposed to be a good thing (provided you know how to implement them correctly)?
stakx
@stakx: You are correct. They are awesome if you know how to implement them correctly. So if your name is Joe Duffy, go for it. If not, **avoid lock free techniques**. Lock free code and security code have a lot in common: both are *very hard to get right*, and *work correctly most of the time even when subtly wrong*.
Eric Lippert
A: 
supercat
A: 

See The Problem With Threads

joe snyder
+3  A: 

There's no way I could offer a better answer than what's already here. But I can offer a concrete example of some multithreaded code that we actually had at my work that was disastrous.

One of my coworkers, like you, was very enthusiastic about threads when he first learned about them. So there started to be code like this throughout the program:

Thread t = new Thread(LongRunningMethod);
t.Start(GetThreadParameters());

Basically, he was creating threads all over the place.

So eventually another coworker discovered this and told the developer responsible: don't do that! Creating threads is expensive, you should use the thread pool, etc. etc. So a lot of places in the code that originally looked like the above snippet started getting rewritten as:

ThreadPool.QueueUserWorkItem(LongRunningMethod, GetThreadParameters());

Big improvement, right? Everything's sane again?

Well, except that there was a particular call in that LongRunningMethod that could potentially block -- for a long time. Suddenly every now and then we started seeing it happen that something our software should have reacted to right away... it just didn't. In fact, it might not have reacted for several seconds (clarification: I work for a trading firm, so this was a complete catastrophe).

What had ended up happening was that the thread pool was actually filling up with long-blocking calls, leading to other code that was supposed to happen very quickly getting queued up and not running until significantly later than it should have.

The moral of this story is not, of course, that the first approach of creating your own threads is the right thing to do (it isn't). It's really just that using threads is tough, and error-prone, and that, as others have already said, you should be very careful when you use them.

In our particular situation, many mistakes were made:

  1. Creating new threads in the first place was wrong because it was far more costly than the developer realized.
  2. Queuing all background work on the thread pool was wrong because it treated all background tasks indiscriminately and did not account for the possibility of asynchronous calls actually being blocked.
  3. Having a long-blocking method by itself was the result of some careless and very lazy use of the lock keyword.
  4. Insufficient attention was given to ensuring that the code that was being run on background threads was thread-safe (it wasn't).
  5. Insufficient thought was given to the question of whether making a lot of the affected code multithreaded was even worth doing to begin with. In plenty of cases, the answer was no: multithreading just introduced complexity and bugs, made the code less comprehensible, and (here's the kicker): hurt performance.

I'm happy to say that today, we're still alive and our code is in a much healthier state than it once was. And we do use multithreading in plenty of places where we've decided it's appropriate and have measured performance gains (such as reduced latency between receiving a market data tick and having an outgoing quote confirmed by the exchange). But we learned some pretty important lessons the hard way. Chances are, if you ever work on a large, highly multithreaded system, you will too.

Dan Tao