34

What is the problem with having static variables (especially within functions) in multithreaded programs?

Thanks.

3 Answers3

31

Initialization is not thread-safe. Two threads can enter the function and both may initialize the function-scope static variable. That's not good. There's no telling what the result might be.

In C++0x, initialization of function-scope static variables will be thread-safe; the first thread to call the function will initialize the variable and any other threads calling that function will need to block until that initialization is complete.

I don't think there are currently any compiler + standard library pairs that fully implement the C++0x concurrency memory model and the thread support and atomics libraries.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Is it really likely that a compiler will initialize a static variable on the first call? From what I have seen in the most commonly-used compilers, they are just treated like globals. I just confirmed this for GCC looking at assembly output of some code that uses a `static int`... – asveikau Jan 04 '11 at 05:09
  • 1
    @asveikau: Certainly, any dynamic initialization that has side effects would definitely have to be done when the function is first called an no sooner. In your example, if you have a `static int` initialized with a constant expression, there is no dynamic initialization and the object is initialized before the function is first entered. (I didn't intend to write a treatise here on the many complications with static initialization; there are many, many details. I only wanted to cover the most problematic case here.) – James McNellis Jan 04 '11 at 05:17
  • Some of the trouble is that I was thinking in C, where AFAIK you can't initialize a `static` with a non-constant expression. I think you can do so in C++. – asveikau Jan 04 '11 at 05:27
  • @asveikau: You can in C++. I don't know about C; I'm not nearly as familiar with C as I am with C++. – James McNellis Jan 04 '11 at 05:36
23

To pick an illustrative example at random, take an interface like asctime in the C library. The prototype looks like this:

 char *
 asctime(const struct tm *timeptr);

This implicitly must have some global buffer to store the characters in the char* returned. The most common and simple way to accomplish this would be something like:

 char *
 asctime(const struct tm *timeptr)
 {
    static char buf[MAX_SIZE];

    /* TODO: convert timeptr into string */

    return buf;
 }

This is totally broken in a multi-threaded environment, because buf will be at the same address for each call to asctime(). If two threads call asctime() at the same time, they run the risk of overwriting each other's results. Implicit in the contract of asctime() is that the characters of the string will stick around until the next call to asctime(), and concurrent calls breaks this.

There are some language extensions that work around this particular problem in this particular example via thread-local storage (__thread,__declspec(thread)). I believe this idea made it into C++0x as the thread_local keyword.

Even so I would argue it's a bad design decision to use it this way, for similar reasons as for why it's bad to use global variables. Among other things, it may be thought of as a cleaner interface for the caller to maintain and provide this kind of state, rather than the callee. These are subjective arguments, however.

asveikau
  • 39,039
  • 2
  • 53
  • 68
2

A static variable usually means multiple invocations of your function would share a state and thus interfere with one another.

Normally you want your functions to be self contained; have local copies of everything they work on and share nothing with the outside world bar parameters and return values. (Which, if you think a certain way, aren't a part of the function anyway.)

Consider:

int add(int x, int y);

definitely thread-safe, local copies of x and y.

void print(const char *text, Printer *printer);

dangerous, someone outside might be doing something with the same printer, e.g. calling another print() on it.

void print(const char *text);

definitely non-thread-safe, two parallel invocations are guaranteed to use the same printer.

Of course, there are ways to secure access to shared resources (search keyword: mutex); this is just how your gut feeling should be.

Unsynchronized parallel writes to a variable are also non-thread-safe most of the time, as are a read and write. (search keywords: synchronization, synchronization primitives [of which mutex is but one], also atomicity/atomic operation for when parallel access is safe.)

aib
  • 45,516
  • 10
  • 73
  • 79