Multithreading: understanding threads

In the first post of this series we’ve talked about four important concepts: state, atomicity, serializability and linearizability. As we’ve seen, these are important concepts that are taken for granted in single threaded programming but that might play some tricks on you when you start doing multithreading programming.

Before going on and talking about data synchronization, I think that it would be a good idea to make a small detour and try to define a thread. Yes, we’ve been talking a lot about multhithreading (which, as you can guess, involves several threads), but we still haven’t introduced any definition for a thread. And that’s what I’ll try to do in this post.

Unlike Unix systems, where a process is the fundamental unit of concurrency, Windows decided to use threads for achieving that purpose (the main reason for this is that processes are much “lighter” in Unix than in Windows). So, even though you also have processes in Windows,the truth is that your code is always executed by a thread and that each process has (at least!) one thread (sometimes called the main thread). In other words,the code you write in a program ends up being executed by a thread. Besides the main thread, a program can also create several other threads to alleviate the main thread from its work (and, of course, this might lead to a performance improvement of that program).

When a process starts several threads, there are a bunch of things which are shared between them.For instance, the virtual memory address space is shared by all threads in a process. This means that threads can share data easily since they’re using the same address space. It also means that you cannot allow threads to access the same memory variable without synchronizing them or you might end with bugs which might be hard to duplicate (as we’ve seen, shared state is a blessing and a curse!)

By now, you must be thinking that a thread isn’t really a *physical* concept. And that’s true, though its state isn’t only logical (ie, it’s not only stuff maintained on the memory), but also includes values stored on the processor’s registers. When a thread is running, it’s mapped into a specific processor and it uses its time slice for doing some work. During this time, the thread can freely access all the shared process resources and it can also use the processor’s registers too. Notice that it keeps track of the instruction that is being currently executed by using an instruction pointer.

When that thread is preempted (which can happen for several reasons – for instance, when it’s time slice ends), the OS needs to persist all the information needed by the thread so that it can be rescheduled for a future run. This means that, besides the shared “virtual” resources, it also needs to persist the values that were stored on the processor’s registers and the IP pointer so that the thread might be resumed in the future. This process (of saving stuff that is on the hardware into memory so that it can be resumed in the future) is called context switching and is really expensive (that’s why you see several multithreading experts say that you should minimize thread context switching).

After being created, a thread might start running immediately or it might be placed on a queue managed by the Windows Thread Scheduler (which, as you might have guessed, is responsible for managing all threads and “running” them on the processors available on the machine). Don’t forget that a thread can be in one of several states along its life cycle (besides being running some code or being suspended or being scheduled to run). For instance, it might block when it starts an IO operation and, in this case, the scheduler knows it can remove it from the processor so that other threads are able to make progress. It’s also important to keep in mind that a thread can end its life in several ways (the desired “death” is when the IP pointer reaches the last instruction that should be executed, but its life can also end if the code that is being executed throws an exception which isn’t handled).

Until now we’ve been talking about threads and still haven’t mentioned anything specific about managed or CLR threads. And the truth is that *currently* a CLR is directly mapped into an unmanaged thread! Despite that, there are some aspects you should keep in mind. The most important thing is that the CLR needs to track of all “managed” threads because there are some specific work that it needs to do in order to ensure proper work of managed applications. For instance, it needs to track info regarding live references so that the GC can do its work correctly. In practice, this means that the CLR adds some specific custom state to a thread when it gets created or when an existing unmanaged thread runs managed code for the first time. That’s how it tracks “managed” threads.

And that’s it for today. There are still a lot of stuff to say about threads, but I guess this is enough for an intro post on this subject. Keep tuned for more on multithreaded programming.