Threading on Windows

Threads are becoming one of the more ubiquitous concepts in programming. Chances are quite good that you’ve a few of them before. But have you ever stopped to think about how a thread works under the hood? There are some obvious things, like allocating a stack for the thread, updating some process structures to track the threads, and so forth. But what about the fuzzy stuff that happens between the kernel bookkeeping of making a thread object and your thread entrypoint code?
The part that I want to focus on right now is the thread startup code, specifically the mechanics of ensuring the C runtime library works. What I’m going to describe is the Windows way of doing things, but I’d be surprised if POSIX threads differed drastically.

On Windows, there are two and a half ways to start a thread. CreateThread is an API exposed from Kernel32, and is the single entrypoint from userland to create a thread. _beginthreadex is an API in the CRT used to create a thread that is safe to use in conjunction with the C runtime library. These two are the primary ways to create at thread on Windows. The “half” way to create a thread is the older _beginthread API in the CRT, but that is not one newer code is supposed to use, so I’m not going to bother delving into it.

The Microsoft documentation is quite hazy on why to use _beginthreadex instead of CreateThread for CRT code. In fact, the only clear documentation comes from a Knowledge Base article from 2005. The conundrum detailed in the article alludes to some of what happens before your entrypoint is called.

The long and short of it boils down to the fact that the C Runtime Library predates the concept of threads. So there are some CRT functions which simply wouldn’t work in a multi-threaded environment. For instance, think about the errno variable. In a single-threaded application you never run into a problem. But in a multi-threaded environment, Thread A could be in the process of writing to errno while Thread B is in the process of reading from it. The only sensible thing is to make errno per-thread!

So what happens when you create a thread with _beginthreadex is that a structure is created to hold all of the per-thread data required by the CRT. This structure is then associated with the created thread via a thread-local storage slot. When the thread ends, the structure is freed. In pseduocode, it looks like this:

The first thing to notice is that the call allocates a structure that holds all of the per-thread CRT information, and it also holds the callback and arguments the user passed in. It does eventually call CreateThread because that’s the only way to make threads on Windows, but it passes in its own callback function. So what does this look like?

This is where the magic happen — the helper callback puts the per-thread data structure in the thread local storage slot for the user thread. Only then can it call the user’s callback function. The result of the user’s callback function is passed to _endthreadex. So what does that code look like?

The long and short of it is: if you don’t call _beginthreadex and _endthreadex when using CRT functions, you can leak data small amounts of data. If you called CreateThread and attempt to access rand (for instance), this thread-local structure won’t be located. So one will be created and added to the thread local storage for you. So things appear to still work. But the key problems are: if you call ExitThread yourself (or just run off the end of your callback function), then this structure will not be freed. What’s more, if you use the CRT signal function, you will crash because there’s no SEH block to handle the signal.

But I’ve found some of this information to be dated, too. For instance, one thing which I noticed is that the CRT includes a DllMain which will automatically allocate and deallocate the thread data in the thread attach and detach messages. So calling ExitThread will still do the right thing if CRT data has been used.

At the end of the day, though, this turns out to still be a quite important distinction that a lot of Win32 programmers don’t know about (including myself, until recently!). For instance, if you use the static CRT library, then the CRT functions will still work since all of the CRT functions allocate the thread local data on demand as-needed, but because there’s no DllMain, exiting the thread will leak data. (Of course, there are a lot of other problems to be encountered when statically linking the CRT beyond simple leaks!) And no matter what, the signal function will crash unless you call _beginthreadex because the structured exception handling filter will not be in place.

Will your code be horribly unstable and cause massive failures if you use CreateThread and call a CRT function? Not likely. All CRT functions that use thread-specific data know how to create that data if need be. And many CRT functions are already thread-safe and don’t require the thread specific data anyway. However, it does still cause per-thread leaks in some situations, so if you make a lot of threads, you will get a lot of leaks. And if you call signal, you will get crashes. So the recommendation still stands: use _beginthreadex if your thread will be calling CRT functions, just to be on the safe side. And do not call ExitThread, because that may not allow the CRT to free its structures — call _exitthreadex instead.

Your email address will not be published. Required fields are marked *

Comment

Name *

Email *

Website

Who

Aaron Ballman is a software engineer for GrammaTech. He has almost two decades of experience writing cross-platform frameworks in C/C++, compiler & language design, and software engineering best practices and is currently a voting member of the C (WG14) and C++ (WG21) standards committees.

In case you can't figure it out easily enough, the views expressed here are my personal views and not the views of my employer, my past employers, my future employers, or some random person on the street. Please yell only at me if you disagree with what you read.