Errata for

Programming with POSIX® Threads

David R. Butenhof

Corrections for 2nd printing:

Page 2. There's a missing paragraph, which should be the second paragraph after the "Asynchronous" callout. The summary of sections skips from 1.3 to 1.5, because I added 1.4 later and missed the list. The paragraph should read "Section 1.4 briefly discusses the example programs in the book. You'll find out what assumptions I've made in creating them, and also how to get the online source code for your own use."

Page 13. At the end of the first full paragraph ("These lines show the header files..."), add "Section 1.9.3 shows the full text of the errors.h header file."

Page 13. The URL for the online source code should be updated to "... is available online through the page for this book, at http://www.awl.com/cseng/titles/0-201-63392-2/".

Page 42. Third paragraph under "Running and blocking" section.

Original text:

Usually this means that some other thread has blocked, or has been preempted by a timeslice -- the blocking (or preempted) thread saves its context and restores the context of the next ready thread to replace itself.

Replacement text:

Usually this means that some other thread has blocked, or has been preempted by a timeslice -- the processor saves the context of the blocking (or preempted) thread, and restores the context of the next ready thread.

Page 44. New paragraphs at the end of the page:

Once a thread is recycled, the thread's ID (pthread_t) is no longer valid. You cannot join with the thread, cancel it, or anything else. The terminated thread's ID, (which may be the address of a system data structure), may be assigned to a new thread. Instead of receiving an ESRCH failure from your call to pthread_cancel, you would instead cancel a different thread.

All system resources are released by thread termination, but you must release program resources owned by the thread. Memory that a thread allocated by calling malloc or mmap may be freed at any time, by any thread having the address. Mutexes, condition variables, and semaphores may be destroyed by any thread, as long as they are unlocked (where appropriate) and there are no waiters. Only the owner of a mutex, however, can unlock the mutex. If a thread terminates while it has a mutex locked, that mutex cannot be used again (it can never be unlocked).

Page 51. In the final paragraph, starting "It is safe to destroy a mutex...", on the second line, replace the phrase "and no additional threads will try to lock the mutex" with "and the mutex is unlocked".

Page 52. There's no space on the page to add much without page break changes, so I'm going to sacrifice an informational comment for a more important warning. Delete the second paragraph, (beginning with "You cannot lock a mutex when the calling thread..."). The first sentence is almost unchanged, and the second sentence is just truncated, but I'll give the whole thing for clarity. Replace with:

You cannot lock a mutex when the calling thread already has that mutex locked. The result of attempting to do so may be an error return, (EDEADLK), or it may be a self-deadlock, where the unfortunate thread waits forever. You cannot unlock a mutex that is unlocked, or that is locked by another thread. Locked mutexes are owned by the thread that locks them. If you need an "unowned" lock, use a semaphore. (Section 6.6.6 discusses semaphores.)

Page 56. In the first full paragraph (labelled "39-49"), line 5, the phrase "maintains a current entry pointer (this)" should be "maintains a current entry pointer (next)".

Page 212. Last paragraph under "Directory Searching". Replace "Refer to program pipe.c in Section 4.1" by "Refer to program crew.c in Section 4.2".

Page 216. Add sentence to end of the first paragraph (beginning with "The synchronous 'hardware context' signals..."):

SIGPIPE is also considered "synchronous".

Page 244. In second-last line of the footnote, "letter l. and the digit 0", the period after "l" should be a comma.

Page 368. The original URL for the book (second web site entry) has changed from the original http://www.aw.com/cp/butenhof/posix.html to the new http://www.awl.com/cseng/titles/0-201-63392-2/

Page 377. The index entry for "pthread_once" is missing a "definition of" subentry (page 131).

Corrections for 4th printing:

Page 17, the last sentence of the first paragraph starts "Four Pthreads calls", but actually the final form of the example uses only 3. It should say "Three Pthreads calls", and the 3rd bullet item in the following list ("pthread_exit [...]") should be deleted.

Page 230, bottom, says "[...] and sigqueue (which queues a signal to a process)." The sigqueue function does not accept a sigevent. Replace the sentence with:

Other functions that accept a struct sigevent include timer_create (which creates a per-process timer) and mq_notify (which provides for notification of a queued message).

Page 315, in the description of pthread_equal. It says "Return value 0 if t1 and t2 are equal, otherwise return nonzero." That's backwards. It should be "Return a nonzero value if t1 and t2 are equal, otherwise return zero."

Corrections for 7th printing:

Page 129, example line 21. The reference to client_threads should be CLIENT_THREADS to fix a hypothetical race condition in the program:

[21] for (count = 0; count < CLIENT_THREADS; count++) {

Page 161, cancel_subcontract.c example. Technically, there ought to be a line, numbered 104, following 103 (the err_abort call) containing "return 0;". (The closing brace on line 104 would become line 105.)

Page 168, third paragraph up from the bottom. "The &lt;limits.h&gt; header defines _PTHREAD_DESTRUCTOR_ITERATIONS". The "_" doesn't belong there. The sentence should begin "The &lt;limits.h&gt; header defines PTHREAD_DESTRUCTOR_ITERATIONS".

Page 205, 2nd paragraph, says "To write a prompt string to stdin and read a response from stdout [...]". That should be "To write a prompt string to stdout and read a response from stdin [...]".

Page 206, flock.c example. This example is fine as coded on essentially any modern UNIX system. However, it was pointed out to me recently that it violates strict ANSI C rules about pointer typecasts. The necessary changes are easy to make, and a good example is always worth while.

Line 37, "char *string;" should be "void *string;".

The "(void**)" should be removed from lines 61, 66, and 71, making them:

[61] status = pthread_join (thread1, &string);
[66] status = pthread_join (thread2, &string);
[71] status = pthread_join (thread3, &string);

And, finally, lines 64, 69, and 74 should be:

[64] printf ("Thread 1: \"%s\"\n", (char*)string);
[69] printf ("Thread 2: \"%s\"\n", (char*)string);
[74] printf ("Thread 3: \"%s\"\n", (char*)string);

Page 218, description of line 12, 4th line, sentence beginning "The volatile storage attribute ensures" should be replaced by the following sentence: The "volatile sig_atomic_t" type tries to ensure that the signal-catching function will write the value to memory."

Page 218, last paragraph (description of lines 47-51). The resume signal is SIGUSR2, not SIGUSR1. The first sentence should read: The resume_signal_handler function will be established as the signal-catching function for the "resume" signal, SIGUSR2.

Page 219, susp.c example, line 12, "volatile int sentinel = 0;" should be:

[12] volatile sig_atomic_t sentinel = 0;

Page 220, susp.c example, line 13, "* to guarentee idempotency" should be:

[13] * to guarantee idempotency.

Page 220, susp.c example, lines 21 through 27 (sigusr1.sa_flags through sigusr2.sa_mask) should be replaced by:

[21] sigusr1.sa_flags = 0;
[22] sigusr1.sa_handler = suspend_signal_handler;
[23] sigemptyset (&sigusr1.sa_mask);
[24] sigaddset (&sigusr1.sa_mask, SIGUSR2);
[25] sigusr2.sa_flags = 0;
[26] sigusr2.sa_handler = resume_signal_handler;
[27] sigemptyset (&sigusr2.sa_mask);

Page 222, susp.c example, line 37, "if (array[i++] == target_thread) {" should be:

[37] if (pthread_equal (array[i++], target_thread)) {

Page 222, susp.c example, line 48, "while (array[i] != 0)" should be:

[48] while (i < bottom && array[i] != 0)

Page 223, from the paragraph describing lines 23-26 of susp.c part 4 through example line 27. Replace with the following. (The paragraph describing lines 45-51 hasn't changed, but is included for reference. Also note that both lines 26 and 27 of the example are blank, which retains the existing line numbering on the following page.)

16-18 The thd_continue function first ensures that the suspend/resume package is initialized by calling pthread_once.

33-39 If the specified thread identifier is not found in the array of suspended threads, then it is not suspended. Return with success.

45-51 Send the resume signal, SIGUSR2. There's no need to wait -- the thread will resume whenever it can, and the thread calling thd_continue doesn't need to know.

[susp.c part 4 thd_continue] [-------------------------------------------------]
[01] /* 
[02]  * Resume a suspended thread by sending it SIGUSR2 to break
[03]  * it out of the sigsuspend() in which it's waiting. If the
[04]  * target thread isn't suspended, return with success.
[05]  */
[06] int
[07] thd_continue (pthread_t target_thread)
[08] {
[09]     int status;
[10]     int i = 0;
[11]
[12]     /*
[13]      * The first call to thd_continue will initialize the
[14]      * package.
[15]      */
[16]     status = pthread_once (&once, suspend_init_routine);
[17]     if (status != 0)
[18]         return status;
[19]
[20]     /*
[21]      * Serialize access to suspend/resume, to make life easier
[22]      */
[23]     status = pthread_mutex_lock (&mut);
[24]     if (status != 0)
[25]         return status;
[26]
[27]

Page 224, susp.c example, line 33, "while (array[[i] != target_thread && i < bottom)" should be:

[33] while (i < bottom && !pthread_equal (array[i], target_thread))

Page 224, susp.c example, line 36, "if (i >= bottom) {" should be, for consistency:

[36] if (i == bottom) {

Page 225, paragraph 2. (Labelled [36-42].) This is poorly worded. It should be revised:

36-42 Threads are created with an attributes object set to create threads detached, rather than joinable. The result is that threads will cease to exist as soon as they terminate, rather than remaining until another thread calls pthread_join. A terminated thread cannot receive a signal, but the standard does not require that pthread_kill fail in this case. An attempt to suspend a terminated (but not detached) thread would hang waiting for the thread to confirm that it was suspending. The standard requires that pthread_kill fail with ESRCH if the thread no longer exists. Depending on this can be risky, because the thread ID may be reused for a new thread, but this example does not create additional threads. Thus, if a thread terminates prematurely, the call to thd_suspend should fail "gracefully" rather than causing the program to hang.

Page 225, paragraph 3. In the 4th line, "It waits another two seconds, suspends each [...]", remove the phrase "waits another two seconds," so the sentence begins "It suspends each [...]".

Page 225, susp.c example, line 10. The comment disagrees with the code. "Every time each thread does 5000 iterations" should be "Every time each thread does 2000 iterations".

Page 232, sigev_thread.c example, line 39. Show the proper type and arguments for "main()". (This doesn't affect the code, but it appears to be the only such oversight in the example code, and it should be consistent.)

[39] int main (int argc, char *argv[])

Page 234: In the shaded info block, 3rd-from-last semaphore function, the name of the function should be sem_trywait, not sem_trywake.

Page 244. Third paragraph, second line: '"cycle" variable that is logically inverted each time [...]' becomes '"cycle" variable that is incremented each time [...]'. Two lines later, "the thread inverts the cycle flag." becomes "the thread increments the cycle." In the paragraph describing example lines 6-12, 3rd line, "And cycle is the flag discussed in the previous paragraph." becomes "And cycle is the counter discussed earlier." NOTE: this change (and the associated following code changes) are "enhancements" rather than "fixes". It was suggested that, if more than one set of threads shared a barrier, it might be possible for the second set to complete a second barrier wait before one or more threads awakened from the earlier barrier wait. If this happened, the toggled "cycle" variable would cause those "old" waiters to continue waiting (the cycle would have been toggled back to match their private values). Changing "cycle" into a counter prevents that from happening in all realistic cases (the unsigned long value would need to wrap back around). The changes are sufficiently simple that it seems worth including them.

Page 245, barrier.h, line 12, change "int" to "unsigned long" and revise the comment:

[12] unsigned long cycle; /* count cycles */

Page 248, barrier.c, line 35. Replace "==" with "!=":

[35] return (status != 0 ? status : status2);

Page 250, barrier.c, line 20, change "barrier->cycle = !barrier->cycle;" to

[20] barrier->cycle++;

Page 251, barrier_main.c, line 18. "Style consistency" error pointed out by an overly astute reader! The white space before the "}" character (in " } thread_t;") should be removed.

Page 259, rwlock.c, line 27, the two "!=" should be ">", for consistency with other similar tests. (The values should never be negative; this isn't a fix, but an improvement to clarity and consistency.)

[27] if (rwl->r_wait > 0 || rwl->w_wait > 0) {

Page 259, rwlock.c, lines 39 and 40, in both lines, "==" should become "!=":

[39] return (status != 0 ? status
[40] : (status1 != 0 ? status1 : status2));

Page 279, workq.c, line 63, use "0x%p" instead of "%#lx" for ANSI C portability:

[63] DPRINTF (("Work queue: 0x%p, quit: %d\n",

Page 284, 2nd line under "Modifying libraries to be thread-safe". Change "strtok or getpwd." to "strtok.".

Page 296; The callout at the bottom of the page, "Sequence races" can occur even when all your code uses mutexes to protect shared data", is misplaced. It should be moved up to immediately precede the first full paragraph on this page, which begins "'Sequence races' may occur when you assume some ordering of events, but that ordering isn't coded into the application."

Page 296, Remove three paragraphs, starting with the paragraph immediately preceding the original location of the callout, "Races aren't always way down there at the level of memory address references", the following paragraph beginning "This race occurs even if readdir is 'thread aware'", which continues onto page 297, and the next paragraph on 297, starting "That's why Pthreads specifies a set of new reentrant functions". These three paragraphs are replaced by this new text, which should be placed at the end of section 8.1.2 on page 297, immediately after the paragraph beginning "Usually a program that doesn't care about ordering", and immediately before the section 8.1.3 header.

Sometimes, "thread-safe" functions aren't enough. The readdir function, for example, generally returns a pointer into the DIR structure that was passed to the function. Thus, calls from different threads using independent DIR structures may already be thread-safe. You might also think that readdir could allow sharing a DIR structure between threads by adding a mutex to the DIR structure that would be locked on each call. The situation is not quite so simple, because the race extends beyond the boundary of the readdir function. Thread A might call readdir, which locks a mutex, finds the next file in the directory, "fileA", unlocks the mutex, and returns a pointer to a struct dirent for fileA. Thread B might concurrently call readdir using the same DIR structure, to find the next file. (They might be implementing a parallel find command.) The second call also locks the mutex, advances to the next directory entry, unlocks the mutex, and returns fileB. Now Thread A follows the pointer it received, which points to information for fileB. No interface has behaved improperly, no data has been corrupted, but fileA was forgotten.

To avoid this problem, the caller must lock a mutex before calling readdir, and keep it locked until after the returned file data had been used, or copied elsewhere. That's why Pthreads specifies readdir_r, which has an additional argument, where the caller specifies a pointer to a struct dirent in which readdir_r should return the next directory entry. Although readdir_r probably doesn't lock a mutex, because sharing the traversal of a single directory isn't common, you can easily share the traversal by locking your own mutex around each call to readdir_r. The improved interface allows each thread to retain, and use, the returned struct dirent data in any way that makes sense, without needing to leave the mutex locked beyond the return from readdir_r. Refer to crew.c, in Section 4.2, for a program that uses readdir_r.

Page 297, last-but-one paragraph in 8.1.2, starting "There are a number of ways you can resolve the getc race." In the sentence beginning "Or you can lock the file across the entire sequence of gets operations in each thread," the word "gets" should be "getc".

Page 312, pthread_attr_getstackaddr function description. Under the ENOSYS error, "stacksize not supported" should read "stackaddr not supported".

Page 317, pthread_mutexattr_setpshared function description. Under the EINVAL error, "attr or detachstate invalid" should be "attr or pshared invalid".

Page 321, pthread_condattr_setpshared function description. Under the EINVAL error, "attr or detachstate invalid" should be "attr or pshared invalid".

Page 323, pthread_cleanup_push function description. Delete the second sentence of the description, "Invoke the cleanup handler if execute is nonzero." (This was "inherited" from pthread_cleanup_pop, and is inappropriate here.)

Page 325, pthread_testcancel function description. Remove the inappropriate hint, "Cancellation is asynchronous. Use pthread_join to wait for termination of thread if necessary." Replace with "Use in compute-bound loops to poll for cancellation requests."

Page 329, pthread_attr_setschedparam function description. Under the ENOTSUP error, "param set to supported value" should be "param set to unsupported value".

Page 330, pthread_attr_setschedpolicy function description. Under the ENOTSUP error, "param set to supported value" should be "policy set to unsupported value".

Page 330, pthread_attr_setscope function description. Under the ENOTSUP error, "contentionscope set to supported value" should be "contentionscope set to unsupported value".

Page 337, funlockfile function description. This has a typo, and fails to recognize ftrylockfile as an equal to flockfile. The first sentence should read "Decrease the lock count for a stdio file stream that was previously locked by a corresponding call to flockfile or ftrylockfile.

Page 339, readdir_r function description. The second sentence, beginning "Whereas readdir retains the current position" should be replaced with "Whereas readdir returns a pointer to the next directory entry, readdir_r writes the next directory entry to the buffer specified by the entry parameter."

Page 343, pthread_sigmask function description. Second row of the table, describing the how parameter value SIG_UNBLOCK. "Resulting set is the intersection of" should be "Resulting set is the difference between".

Page 345, sem_destroy function description. The first error description, "[EINVAL] value exceeds SEM_VALUE_MAX.", does not belong with sem_destroy. It should be moved from sem_destroy to sem_init, further down the page. It should be placed immediately after the line "[EINVAL] sem is not a valid semaphore."

Page 350, pthread_mutexattr_gettype function description. The description, "Specify the type of mutexes created with attr." should read "Determine the type of mutexes created with attr."

Page 351, pthread_mutexattr_settype function description. The description, "Determine the type of mutexes created with attr." should read "Specify the type of mutexes created with attr."

Page 352, pthread_getconcurrency description. The function prototype, "int pthread_getconcurrency ();" should read int pthread_getconcurrency (void);

Page 352, pthread_setconcurrency description. The function prototype should say for pthread_setconcurrency, not pthread_getconcurrency: int pthread_setconcurrency (int new_level);

Page 360, 4th paragraph (including first partial paragraph), beginning with "This is accomplished by adding the condition variable attribute clock." In the next sentence, "thread" should be "condition variable": You can set the clock attribute in a condition variable attributes object using pthread_condattr_setclock and request the current value by calling pthread_condattr_getclock.

© 2001 Dave Butenhof