Historically, most code has been designed for single-threaded programs. This is especially true for most of the library routines called from C programs. The following implicit assumptions were made for single-threaded code:
When you write into a global variable and then, a moment later, read from it, what you read is exactly what you just wrote.
You do not need synchronization because there is nothing to synchronize with.
The next few examples discuss some of the problems that arise in multithreaded programs because of these assumptions, and how you can deal with them.
Traditional, single-threaded C and UNIX have a convention for handling errors detected in system calls. System calls can return anything as a functional value (for example, write() returns the number of bytes that were transferred). However, the value -1 is reserved to indicate that something went wrong. So, when a system call returns -1, you know that it failed.
extern int errno; ... if (write(file_desc, buffer, size) == -1) { /* the system call failed */ fprintf(stderr, “something went wrong, “ “error code = %d\n”, errno); exit(1); } ...
Rather than return the actual error code (which could be confused with normal return values), the error code is placed into the global variable errno. When the system call fails, you can look in errno to find out what went wrong.
Now consider what happens in a multithreaded environment when two threads fail at about the same time, but with different errors. Both expect to find their error codes in errno, but one copy of errno cannot hold both values. This global variable approach simply does not work for multithreaded programs.
Threads solves this problem through a conceptually new storage class—thread-specific data. This storage is similar to global storage in that it can be accessed from any procedure in which a thread might be running. However, it is private to the thread—when two threads refer to the thread-specific data location of the same name, they are referring to two different areas of storage.
So, when using threads, each reference to errno is thread specific because each thread has a private copy of errno. This is achieved in this implementation by making errno a macro that expands to a function call.