NSPR 2.0: Thread synchronization sample

##
Thread Synchronization Sample

Declarations and Notified and Switch show a single sample program, `switch.c`, that illustrates basic techniques of NSPR thread synchronization. This program is also available in the Samples directory.

Declarations and Notified function

`1 /* Copyright © 1998 Netscape Communications Corporation */
` `2
` `3 #include "prinit.h"
` `4 #include "prlock.h"
` `5 #include "prcvar.h"
` `6 #include "prmem.h"
` `7 #include "prinrval.h"
` `8 #include "prlog.h"
` `9 #include "prthread.h"
` `10 #include "prprf.h"
` `11 #include "plerror.h"
` `12
` `13 #define INNER_LOOPS 100
` `14 #define DEFAULT_LOOPS 100
` `15 #define DEFAULT_THREADS 10
` `16
` `17 typedef struct Shared
` `18 {
` `19 PRLock *ml;
` `20 PRCondVar *cv;
` `21 PRBool twiddle;
` `22 PRThread *thread;
` `23 struct Shared *next;
` `24 } Shared;
` `25
` `26 static PRFileDesc *debug_out = NULL;
` `27 static PRBool debug_mode = PR_TRUE, verbosity = PR_FALSE, failed = PR_FALSE;
` `28 static Shared home;
` `29
` `30 static void Help(void);
` `31 static void PR_CALLBACK Notified(void *arg);
` `32 static PRIntn PR_CALLBACK Switch(PRIntn argc, char **argv);
` `33
` `34 /* Root function for the threads in this sample (except primordial thread, which runs main) */
` `35 static void PR_CALLBACK Notified(void *arg)
` `36 {
` `37 Shared *shared = arg;
` `38 PRStatus status = PR_SUCCESS;
` `39 while (PR_SUCCESS == status)
` `40 {
` `41 PR_Lock(shared->ml);
` `42 while (shared->twiddle && (PR_SUCCESS == status))
` `43 status = PR_WaitCondVar(shared->cv, PR_INTERVAL_NO_TIMEOUT);
` `44 if (verbosity) PR_fprintf(debug_out, "+");
` `45 shared->twiddle = PR_TRUE;
` `46 shared->next->twiddle = PR_FALSE;
` `47 PR_NotifyCondVar(shared->next->cv);
` `48 PR_Unlock(shared->ml);
` `49 }
` `50 } /* Notified */
` `51
`

The root function, `Notified`, for the threads in this sample (except for the primordial thread, which runs `main`) starts after line 34 of Declarations and Notified. The threads are chained together, with the primordial thread at the end of the chain.

Each thread blocks on a condition variable (`shared-cv`), waiting for the Boolean expression `shared->twiddle` to become `PR_FALSE`. Notice that the condition variable wait is repeated while the Boolean expression `shared->twiddle` is `PR_TRUE`. This `while` loop around a condition variable wait is a common idiom.

The condition variable wait fails with `PR_PENDING_INTERRRUPT_ERROR` if the thread is interrupted, in which case the thread breaks out of the nested `while` loops and exits.

When the thread is notified and `shared->twiddle` has become `PR_FALSE`, it sets `shared->next->twiddle` to `PR_FALSE` and notifies the next thread down in the chain. It also sets its own `shared->twiddle` back to `PR_TRUE`, in preparation for the next iteration.

The `Switch` function shown in Switch is really the `main` function of this program. The `main` function simply passes `Switch` to `PR_Initialize`.

`Switch` and `main` functions

`52 static PRIntn PR_CALLBACK Switch(PRIntn argc, char **argv)
` `53 {
` `54 PRStatus status;
` `55 PRBool help = PR_FALSE;
` `56 PRUintn concurrency = 1;
` `57 Shared *shared, *link;
` `58 PRIntervalTime timein, timeout;
` `59 PRBool global_threads = PR_FALSE;
` `60 PRThreadScope thread_scope = PR_LOCAL_THREAD;
` `61 PRUintn thread_count, inner_count, loop_count, average;
` `62 PRUintn thread_limit = DEFAULT_THREADS, loop_limit = DEFAULT_LOOPS;
` `63
` `64 if (help) return -1;
` `65
` `66 if (PR_TRUE == debug_mode)
` `67 {
` `68 debug_out = PR_STDOUT;
` `69 PR_fprintf(debug_out, "Test parameters\n");
` `70 PR_fprintf(debug_out, "\tThreads involved: %d\n", thread_limit);
` `71 PR_fprintf(debug_out, "\tIteration limit: %d\n", loop_limit);
` `72 PR_fprintf(debug_out, "\tConcurrency: %d\n", concurrency);
` `73 PR_fprintf(
` `74 debug_out, "\tThread type: %s\n",
` `75 (PR_GLOBAL_THREAD == thread_scope) ? "GLOBAL" : "LOCAL");
` `76 }
` `77
` `78 PR_SetConcurrency(concurrency);
` `79
` `80 /* "home" is "Shared" object for the primordial thread, at the end of the chain. */
` `81 link = &home;
` `82 home.ml = PR_NewLock();
` `83 home.cv = PR_NewCondVar(home.ml);
` `84 home.twiddle = PR_TRUE;
` `85 home.next = NULL;
` `86 timeout = 0;
` `87
` `88 /* Create "thread_limit" number of additional threads, each with its own "Shared" object. */
` `89 for (thread_count = 1; thread_count <= thread_limit; ++thread_count)
` `90 {
` `91 shared = PR_NEWZAP(Shared);
` `92 shared->ml = home.ml;
` `93 shared->cv = PR_NewCondVar(home.ml);
` `94 shared->twiddle = PR_TRUE;
` `95 shared->next = link;
` `96 link = shared;
` `97 shared->thread = PR_CreateThread(
` `98 PR_USER_THREAD, Notified, shared,
` `99 PR_PRIORITY_HIGH, thread_scope,
` `100 PR_JOINABLE_THREAD, 0);
` `101 PR_ASSERT(shared->thread != NULL);
` `102 if (NULL == shared->thread)
` `103 failed = PR_TRUE;
` `104 }
` `105
` `106 /* "Shared" now points to the head of the chain, and "home" is the tail of the chain. */
` `107 for (loop_count = 1; loop_count <= loop_limit; ++loop_count)
` `108 {
` `109 timein = PR_IntervalNow();
` `110 for (inner_count = 0; inner_count < INNER_LOOPS; ++inner_count)
` `111 {
` `112 PR_Lock(home.ml);
` `113 home.twiddle = PR_TRUE;
` `114 shared->twiddle = PR_FALSE;
` `115 PR_NotifyCondVar(shared->cv);
` `116 while (home.twiddle)
` `117 {
` `118 status = PR_WaitCondVar(home.cv, PR_INTERVAL_NO_TIMEOUT);
` `119 if (PR_FAILURE == status)
` `120 failed = PR_TRUE;
` `121 }
` `122 PR_Unlock(home.ml);
` `123 }
` `124 timeout += (PR_IntervalNow() - timein);
` `125 }
` `126
` `127 if (debug_mode)
` `128 {
` `129 average = PR_IntervalToMicroseconds(timeout)
` `130 / (INNER_LOOPS * loop_limit * thread_count);
` `131 PR_fprintf(
` `132 debug_out, "Average switch times %d usecs for %d threads\n",
` `133 average, thread_limit);
` `134 }
` `135 /* Test completed. Remainder of sample cleanly shuts down the test. */
` `136 link = shared;
` `137 for (thread_count = 1; thread_count <= thread_limit; ++thread_count)
` `138 {
` `139 if (&home == link) break;
` `140 status = PR_Interrupt(link->thread);
` `141 if (PR_SUCCESS != status)
` `142 {
` `143 failed = PR_TRUE;
` `144 if (debug_mode)
` `145 PL_FPrintError(debug_out, "Failed to interrupt");
` `146 }
` `147 link = link->next;
` `148 }
` `149
` `150 for (thread_count = 1; thread_count <= thread_limit; ++thread_count)
` `151 {
` `152 link = shared->next;
` `153 status = PR_JoinThread(shared->thread);
` `154 if (PR_SUCCESS != status)
` `155 {
` `156 failed = PR_TRUE;
` `157 if (debug_mode)
` `158 PL_FPrintError(debug_out, "Failed to join");
` `159 }
` `160 PR_DestroyCondVar(shared->cv);
` `161 PR_DELETE(shared);
` `162 if (&home == link) break;
` `163 shared = link;
` `164 }
` `165 PR_DestroyCondVar(home.cv);
` `166 PR_DestroyLock(home.ml);
` `167
` `168 PR_fprintf(PR_STDOUT, ((failed) ? "FAILED\n" : "PASSED\n"));
` `169 return ((failed) ? 1 : 0);
` `170 } /* Switch */
` `171
` `172 PRIntn main(PRIntn argc, char **argv)
` `173 {
` `174 PRIntn result;
` `175 PR_STDIO_INIT();
` `176 result = PR_Initialize(Switch, argc, argv, 0);
` `177 return result;
` `178 } /* main */
` `179
` `180 /* switch.c */
`

The `home` object defined after line 80 in Switch is the `Shared` object for the primordial thread. It is at the end of the chain.

The `Switch` function creates `thread_limit` number of additional threads after line 87, each with its own `Shared` object. The `Shared` objects are chained together. The `shared->twiddle` value for these objects is initialized to `PR_TRUE`, so that the threads are all blocked initially.

By line 106, `Shared` points to the head of the chain, and `home` is the tail of the chain. The primordial thread then sets `shared->twiddle` to `PR_FALSE` and notifies `shared->cv` to kick off the domino effect. The primordial thread then blocks on its own condition variable `home.cv` at the end of the chain. This process is repeated by a nested `for` loop.

By line 135, the test is finished. The code that follows cleanly shuts down the test. The primordial thread interrupts all the threads so that they know it's time to exit. Then the primordial thread joins all the threads to synchronize with their termination.

Last Updated: Mon Jul 13 15:31:58 PDT 1998

Copyright © 1998 Netscape Communications Corporation