Chapter 12
12.1 | This is not a multithreading problem, as one might first guess. The standard I/O routines are indeed thread-safe. When we call fork, each process gets a copy of the standard I/O data structures. When we run the program with standard output attached to a terminal, the output is line buffered, so every time we print a line, the standard I/O library writes it to our terminal. However, if we redirect the standard output to a file, then the standard output is fully buffered. The output is written when the buffer fills or the process closes the stream. When we fork in this example, the buffer contains several printed lines not yet written, so when the parent and the child finally flush their copies of the buffer, the initial duplicate contents are written to the file. | 12.3 | Theoretically, if we arrange for all signals to be blocked when the signal handler runs, we should be able to make a function async-signal-safe. The problem is that we don't know whether any of the functions we call might unmask a signal that we've blocked, thereby making it possible for the function to be reentered through another signal handler. | 12.4 | On FreeBSD 5.2.1, we get a continuous spray of error messages, and after a while, the program drops core. With gdb, we are able to see that the program was stuck in an infinite loop during initialization. The program initialization calls thread initialization functions, which call malloc. The malloc function, in turn, calls getenv to find the value of the MALLOC_OPTIONS environment variable. Our implementation of getenv calls pthread functions, which then try to call the thread initialization functions. Eventually, we hit an error and get stuck in a similar infinite loop after abort is called. After half a million or so stack frames, the process exits, generating a core dump. | 12.5 | We still need fork if we want to run a program from within another program (i.e., before calling exec). | 12.6 | Figure C.12 shows a thread-safe sleep implementation that uses select to delay for the specified amount of time. It is thread-safe because it doesn't use any unprotected global or static data and calls only other thread-safe functions.
Figure C.12. A thread-safe implementation of sleep
#include <unistd.h>
#include <time.h>
#include <sys/select.h>
unsigned
sleep(unsigned nsec)
{
int n;
unsigned slept;
time_t start, end;
struct timeval tv;
tv.tv_sec = nsec;
tv.tv_usec = 0;
time(&start);
n = select(0, NULL, NULL, NULL, &tv);
if (n == 0)
return(0);
time(&end);
slept = end - start;
if (slept >= nsec)
return(0);
return(nsec - slept);
}
| 12.7 | The implementation of a condition variable most likely uses a mutex to protect its internal structure. Because this is an implementation detail and therefore hidden, there is no portable way for us to acquire and release the lock in the fork handlers. Since we can't determine the state of the internal lock in a condition variable after calling fork, it is unsafe for us to use the condition variable in the child process.
|
|