1. Covering Multithreading Basics
4. Programming with Synchronization Objects
5. Programming With the Solaris Software
6. Programming With Solaris Threads
Providing for Static Local Variables
Deadlocks Related to Scheduling
Some Basic Guidelines for Threaded Code
Parallelizing a Loop on a Shared-Memory Parallel Computer
Historically, most code has been designed for single-threaded programs. This code design is especially true for most of the library routines that are 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 the variable, what you read is exactly what you just wrote.
A write into nonglobal, static storage, and moment later, a read from the variable results in a read of exactly what you just wrote.
You do not need synchronization because concurrent access to the variable is not invoked.
The following examples discuss some of the problems that arise in multithreaded programs because of these assumptions, and how you can deal with the problems.
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 the call failed.
Example 9-1 Global Variables and errno
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 returning 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 threads expect to find their error codes in errno, but one copy of errno cannot hold both values. This global variable approach does not work for multithreaded programs.
Threads solve this problem through a conceptually new storage class: thread-specific data. This storage is similar to global storage. Thread-specific data can be accessed from any procedure in which a thread might be running. However, thread-specific data is private to the thread. When two threads refer to the thread-specific data location of the same name, the threads 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. A reference to errno as thread-specific is achieved in this implementation by making errno a macro that expands to a function call.