Although this is slowly changing, most desktop computers today have only one processor. That means it can only run one thing at any given moment, and all those times you thought your computer was running multiple programs at once were just illusions. Surprised? The trick is that even though your processor runs only one thing at a time, it doesn't have to completely finish a task before starting the next one. The computer can simulate running many programs at once by spending a tenth of a second on the first process, a tenth of a second on the next, and so on. From the computer's viewpoint, only one thing is happening at a time. But from the user's viewpoint, everything looks like it's happening concurrently.
Most developers are at least partially aware that something like that must be going on. But it's far less obvious that each program can do the same thing on a smaller scale: In addition to having multiple programs that appear to run at the same time, every program can be divided into multiple threads, each of which performs a separate task. When multiple threads are used correctly, your program can execute several tasks at once, and even better, it can still feel more responsive to the user.
Most programs spend most of their time waiting—waiting for the user to type something in, waiting for the hard drive to read some data, waiting for a response from a remote web server, etc. Threads let us take advantage of these delays—when one thread is in a wait state, other threads can run useful work until the first thread is done waiting.
Have you ever tried to download a web page with Internet Explorer, gotten bored, and then pushed the Back button to abort the page load? That doesn't sound like it's very hard, but if the program were single threaded, then it would have to finish each task before starting the next one, and it wouldn't be able to notice that you pushed the Back button until the page had finished downloading. Think what a frustrating experience that would be! Or take another example: Have you ever noticed that Microsoft Word automatically saves your program every 10 minutes, even while doing something else (such as displaying the words you type)? Well-designed multithreaded programs work so seamlessly that you never even realize anything unusual is happening at all.
It's worth pointing out here that threads are not independent of each other the way processes are. All the threads in a process are tied together: They live in the same memory address space, they can easily share data, they know of each other's existence in the sense that one thread can explicitly wait for another to finish before continuing, and an unhandled exception on any one thread will result in all the others crashing, too. It's this interconnectedness of threads that produces most of their bugs. Most threads would work perfectly fine by themselves. The bugs come when multiple threads interact.
The most important piece of advice about debugging threads is this: Whenever possible, avoid the issue entirely. Don't even write multithreaded code in the first place unless you're sure you need it. Of course, by the time you've begun debugging a multithreaded program, this piece of advice will be too little, too late. But the fact remains that many programs don't actually need multiple threads, and you should spend substantial time researching the issue before writing multithreaded code.
My team fell into that trap once. We were building a tool for reading gigabytes of records from disk, performing heavy computations on each record, and then summarizing the result. One developer knew that much of the computer's time would be spent waiting on disk reads, so he designed a multithreaded architecture that allowed several threads to read and process data independently. That way, whenever one thread got stuck waiting on disk reads, another thread could get work done.
The idea was nice in theory, but in practice, all the threads spent the vast majority of their time waiting to use the disk drive. Since our disk had only one drive head, it could handle only one request at a time. The use of multiple threads allowed us to do a little processing while other threads waited on the disk read—but since the processing time was negligible compared to the time spent reading the disk, the gain from multithreading in this case was very small, and certainly not enough to justify the additional complexity and debugging.
Before implementing that multithreaded architecture, we should have spent more time profiling the basic loop to see where the time was being spent and to verify that multithreading would have made sense. Fortunately, we noticed and corrected this problem early in the development cycle; but we should have caught it earlier still. Whenever you're about to add the complexity of multiple threads to your project, spend some time prototyping and researching to make sure it's necessary.