written by Walter Bright
May 21, 2009
Back when multithreaded programming first appeared, people realized there was a problem with the C standard library. It relied on global variables like errno which could be read or set by any thread. In other words, a single instance of errno was shared among multiple threads.
This clearly wasn’t going to work very well, so C runtime libraries got hacked up in various ways, and errno was turned into a macro that did some magic so that each thread saw their own version of errno. In other words, errno (and the other global state the C runtime library relied on) became thread local.
Over time this facility bubbled up to the application programmer in the form of a C compiler extension that allowed the declaration of thread-local global variables. Every compiler, of course, did it a different way and some didn’t do it at all (and still don’t, see gcc for the Mac OSX). (Fortunately, TLS (thread local storage) will be standardized for C++0x, so this situation should improve soon.)
It isn’t just languages that have poor support for TLS, the operating systems do, too. Just try to write a garbage collector that can reliably find all the TLS allocated to a process, just try, my pretty. It can’t be done on Windows, Linux, or Mac OSX, though they all fall short in different ways. Windows, for example, can provide you with a pointer to the base of the TLS but stubbornly won’t give a clue as to how large it is. Linux will tell you how large it is, but won’t tell you where it starts. As far as I can tell, Mac OSX has no concept of TLS. I’m hoping that the imminent standardization of TLS in C++0x will finally get some TLS lovin’ from the OS developers.
But that isn’t the real problem with TLS. The real problem is, nobody uses it. It’s been in the D programming langage compiler for the last year, and I know nobody uses it because I found some bugs in its implementation that prevented more than trivial uses of it. I can’t even recall running across any code in C or C++ that uses it. I can’t recall ever seeing TLS used in a C or C++ programming book.
And essentially, they’re all wrong (!). Remember, global and static variables are implicitly shared. As soon as you try to make your program multithreaded, or someone uses your fabulous library in a multithreaded environment, and no attempt was made to synchronize access to those globals, they all fail just like errno. Now you’re faced with:
“I’ve got to make my code multithreaded. How do I even find all the statics and globals in my 100,000 line codebase?”
That’s certainly a daunting problem, because you can’t grep for them. Statics and globals can be declared in many diverse guises. You’re stuck with either visually going through the code line by line (and then having to do it again for every maintenance update of the code) or if you’re lucky you can instrument a compiler to flag every global.
The answer is to flip things around. Have statics and globals default to being TLS. To make a shared global, explicitly qualify it with shared. This has immediate benefits:
- All shared globals become easily greppable.
- Only a small minority of globals actually need to be shared, so little code will need to be altered.
- It’s unlikely that someone would type shared accidentally, so the programmer who marked them as shared would be putting out a clear signal that the global was used for inter-thread communication. There’s no more guessing about whether a global is meant to be shared or not.
- If a global is meant to be shared, but is not marked as such, it will go into TLS. This means the shared communication won’t work at all, and the bug will be apparent. Implicit sharing, on the other hand, leads to code that works one day and erratically fails the next.
Implicit TLS has just been implemented in D, and it already has uncovered at least a dozen latent sharing bugs in the runtime library.
Thanks to David Held and Jason House for reviewing this.