Thread Basics In Windows And The Common Language Runtime

In this post I will be discussing threads in the CLR, and the general threading model used by Windows. Threads, though they are becoming less and less important in .NET, being replaced by async/await, the TPL, etc. still have uses and are good to know about, even just from the standpoint of better understanding the available concurrent options available in .NET. Let’s jump in.

A brief history of threads in Windows

In the olden days, Windows was single threaded, meaning it operated by doing one single task at a time, then passing the baton off to another thing so to speak. This meant that if a program got into an infinite loop, it was the end for the operating system, and your only option was to restart the machine. In Windows NT, Microsoft introduced processes, which are still used to this day, to address this issue. A process is just a collection of resources used by an application, say Explorer or Microsoft Word. Each process was then given virtual address space so that processes in effect had their own little segment of memory that couldn’t be touched by other applications.

A speedy look at virtualization

Virtualization is the simple idea of mapping real memory addresses to dummy memory addresses. Think of a scenario where you have a bunch of houses on a street, and for each real house number, you have a fake house number, and these real and fake numbers are tracked in a spreadsheet. This is pretty much exactly how virtualization works. In Windows, virtual memory addresses allow you to have a seemingly continuous block of memory that starts at memory address 0, that can be mapped to either an actual continuous block of memory, or several fragmented sections of memory. This memory is allocated in pages, which are small chunks of memory allocated by the operating system. In a virtualized environment, you have pages, virtual pages, and page tables. Pages are the actual bits of memory the system maps virtual pages to, and page tables track which portions of virtual memory are mapped to each portion of real memory.

Virtualization in Windows NT had added security bonuses because applications couldn’t touch other applications memory, and it protected against a lot of viruses that operated on the basis of attacking at very specific memory addresses.

And overview of Windows threads

If virtualization addresses memory concerns, threads addresses applications hogging the CPU if they got stuck in a loop or hung for long periods of time. Threads allow multiple processes to run, which means if one screws up, it doesn’t hose the whole system.

CLR threads are different than actual Windows threads, so let’s cover Windows threads first. In Windows, you have the task scheduler, and it’s job is to queue up application threads and give them time slices to work with the CPU. To better understand threads in Windows, we will look very briefly at the following components of Windows threads:

  • Thread kernel object
  • Thread environment block
  • User mode stack
  • Kernel mode stack
  • DLL thread attach/detach notifications
Thread kernel objects

The thread kernel object simply holds data relating to the thread, most notably the thread context. The thread context holds the state of the CPU registers from when it was last queued up by the task scheduler, the reason for this is because Windows queues up threads by doing context switches, something I’ll cover in a moment.

Thread environment block

The thread environment block is a single page of memory that has the threads exception handling chain, local storage, and structures for things like GDI and OpenGL.

User mode and kernel mode stack

In virtualized environments, all processes are given a block of memory from the operating system, including processes which are close to the metal like the kernel. Pages in virtualized environments get what are known as protection rings, and depending on the needs of the process, the protection ring on certain pages will vary from process to process. As an example, the kernel gets a protection level of 0, meaning it has access to the most assembly instructions available on that CPU, whereas a typical application gets put in protection ring 3, the most restricted ring, drivers may fall anywhere from 1 to 2, etc. The point is, some processes get different privileges. Processes can be in one of two modes, user or kernel. The kernel gets run in kernel mode, which in page tables gets flagged as exclusive to privileged code. This means that if an application tries to touch kernel memory when it is running in user mode, a page fault gets triggered. Why is this set up this way? Because it allows the kernel to be safe from harm from any outside applications that try to touch it.

That explains user mode and kernel mode, but what is the stack for each you may ask? Well first off, user mode stack is easy enough, it’s the portion of the threads memory that holds local variables, arguments, memory addresses for what needs run next, etc. The kernel mode stack is a bit trickier. The kernel mode stack is essentially a stack that the kernel itself is exclusively allowed to view. The kernel oftentimes needs local data from the thread, but remember that the thread operates in user mode, so it isn’t allowed to touch kernel memory. In Windows, the user mode stack is allowed to have it’s kernel function arguments copied into the kernel mode stack. Once this copy operation occurs, the kernel mode stack is validated to make sure nothing sketchy is going on, and because user mode applications can’t touch the kernel, by the time the validation of these arguments happens, there’s no real chance of the data being modified for malicious purposes. It’s like getting a package from a sketchy deliver guy, and closing and locking your door and checking the package, then handing it off to your beloved grandmother, the deliver man can’t get in anymore because the door is locked, and you made sure the package is all clear, so your sweet grandmother can rest easy knowing nothing more can happen with the package (grandmas may not be the best way to explain things, but we’ll roll with it).

DLL attach and detach

We’ve not yet established context switches. A context switch is where the task scheduler gives a thread a time slice, and some time later, swaps the thread out for a new thread, this has to occur because only so many threads can run at once depending on the processor. When a context switch occurs, the operating system halts everything, threads are swapped, and the action resumes (No, I don’t mean literally everything stops, but threads being swapped do stop). When a thread is stopped and a new one gets started up, it loads the data from it’s context into the CPU registers, determines where it left off, and resumes. Another thing that happens when a thread is created, is the DLLMain of every single loaded unmanaged dll for that particular process gets called, passing in a DLL_THREAD_ATTACH flag. Whenever a thread dies, again all DLLMain functions get called, this time passing DLL_THREAD_DETACH. This means that for an application that has say 100 unmanaged dlls loaded, every time a thread is spawned, 100 functions are called, and whenever a thread dies, 100 functions are called, so for every threads lifetime, it will incur 200 extra function calls. If you use threads effectively, this is no problem, but if you don’t use them well, this can actually hurt you.

A note on DLLMain

If you’ve ever written a dll, you know what DLLMain is, if you haven’t, all that DLLMain is, is it’s the main function equivalent for a dll.

Thread Priorities

You may be wondering how the task scheduler decides which threads get to work with the CPU next. The answer is thread priority. In Windows, threads are assigned a priority level when they are spawned. The range of priority levels in Windows range from 0 – 31. Only one thread is at priority 0, and that’s a special thread that zeroes out memory in RAM when no other threads need to do anything. The task scheduler will always give time to highest priority threads first. Priority 31 threads are threads that no matter what will always execute first, this sounds like it would be beneficial from a developers point of view, because your threads will always get priority right? Well that’s sort of the problem, at priority 31, your thread will get priority over even things like the mouse, keyboard, etc. so priority 31 threads can actually be detrimental to the user experience. Once no more priority 31 threads need run, priority 30 threads are run, then 29, 28, etc.

Priorities can be applied at both the process level and the thread level in Windows. At the process level, you can specify a priority level as being idle, below normal, normal, above normal, high, and real time. Again consider that real time can take priority over literally everything else, even network transfers, keyboard input etc. Once you apply a process priority level, thread priority levels are applied relative to the process priority level. Threads can have priority levels assigned as idle, lowest, below normal, normal, above normal, highest, and time critical. There is a chart out there which shows what the true priority level of a thread will be based on it’s process priority level and it’s relative thread level. For example, a process at real time priority, combined with a thread at time critical will be assigned an actual priority level of 31, idle process level with a time critical thread gets set to an actual priority level of 15, etc.

It’s also useful to note that Windows will always stop a lower priority thread when a higher priority thread needs to work, which means when you spawn high priority threads, you’re causing more context switches.

CLR background and foreground threads

Threads in the CLR can be categorized as being foreground threads or background threads. Foreground threads are the thing that will determine whether the app is running or not, because if all foreground threads stop running, all background threads will also stop running without exception. This means that if you make all of your threads foreground threads, the user will have to wait on your threads to finish before the app truly closes (not in all circumstances, just in general). Background threads run only when foreground threads are running, and for that reason, if you have something that absolutely has to be done right then and there, a foreground thread should be your go to. If you have something that can be done, but isn’t critical to have happen, it should happen as a background thread.

In the CLR, threads are set to be foreground threads by default, meaning the CLR is setup to assume your threads are top priority. If you need to change this, the System.Thread class exposes an IsBackground property you can modify as follows:

thread1.IsBackground = true;
thread1.Start();

To actually create a thread in C#, you instantiate an instance of the System.Thread class. One constructor available to you in the thread class is the following:

public Thread(ParameterizedThreadStart start);

This type of thread allows you to assign a method to be executed by the thread. Let’s look at an example of this:

private static void RandomThreadMethodToExecute(Object state) {
	Console.WriteLine("state = {0}", state);
}

// Somewhere else in the program...
Thread newThread = new Thread(RandomThreadMethodToExecute);
newThread.Start(5);

This thread will now print out the number passed to it, in this case 5.

Wrap up

That’s all I’ve got for now, if you have any concerns with anything I’ve shared, feel free to reach out to me and let me know and I will get around to correcting it as soon as possible. I will be covering threads in the CLR in more detail in future posts, so if you liked this post, be sure to bookmark the blog or follow the blog and check back every Monday, Wednesday and Friday when I will be posting. Thanks for taking the time to read, and until next time.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Advertisements
Advertisements
%d bloggers like this: