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
.
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