1#include "../Condition.h"
2#include "../Mutex.h"
3#include "../Thread.h"
4
5#include <boost/bind.hpp>
6
7#include <signal.h>
8
9// Interface of Waiter, also takes care of mutex_ & cond_ init and destroy.
10class Waiter : boost::noncopyable
11{
12 public:
13  virtual void wait() = 0;
14  virtual void signal() = 0;
15
16 protected:
17  Waiter()
18  {
19    pthread_mutex_init(&mutex_, NULL);
20    pthread_cond_init(&cond_, NULL);
21  }
22
23  ~Waiter()
24  {
25    pthread_mutex_destroy(&mutex_);
26    pthread_cond_destroy(&cond_);
27  }
28
29  pthread_mutex_t mutex_;
30  pthread_cond_t cond_;
31};
32
33// Version 1: orininal from the book
34// Incorrect, could lose signal
35class Waiter1 : public Waiter
36{
37 public:
38  void wait() override
39  {
40    pthread_mutex_lock(&mutex_);
41    pthread_cond_wait(&cond_, &mutex_);
42    pthread_mutex_unlock(&mutex_);
43  }
44
45  void signal() override
46  {
47    pthread_cond_signal(&cond_);
48  }
49};
50
51// Version 2: signal in lock
52// Incorrect, could lose signal
53class Waiter2 : public Waiter
54{
55 public:
56  void wait() override
57  {
58    pthread_mutex_lock(&mutex_);
59    pthread_cond_wait(&cond_, &mutex_);
60    pthread_mutex_unlock(&mutex_);
61  }
62
63  void signal() override
64  {
65    pthread_mutex_lock(&mutex_);
66    pthread_cond_signal(&cond_);
67    pthread_mutex_unlock(&mutex_);
68  }
69};
70
71// Version 3: add a boolean member
72// Incorrect, spurious wakeup
73class Waiter3 : public Waiter
74{
75 public:
76  void wait() override
77  {
78    pthread_mutex_lock(&mutex_);
79    if (!signaled_)
80    {
81      pthread_cond_wait(&cond_, &mutex_);
82    }
83    pthread_mutex_unlock(&mutex_);
84  }
85
86  void signal() override
87  {
88    pthread_mutex_lock(&mutex_);
89    signaled_ = true;
90    pthread_cond_signal(&cond_);
91    pthread_mutex_unlock(&mutex_);
92  }
93
94 private:
95  bool signaled_ = false;
96};
97
98// Version 4: wait in while-loop
99// Correct, signal before unlock
100class Waiter4 : public Waiter
101{
102 public:
103  void wait() override
104  {
105    pthread_mutex_lock(&mutex_);
106    while (!signaled_)
107    {
108      pthread_cond_wait(&cond_, &mutex_);
109    }
110    pthread_mutex_unlock(&mutex_);
111  }
112
113  void signal() override
114  {
115    pthread_mutex_lock(&mutex_);
116    signaled_ = true;
117    pthread_cond_signal(&cond_);
118    pthread_mutex_unlock(&mutex_);
119  }
120
121 private:
122  bool signaled_ = false;
123};
124
125// Version 5: wait in while-loop
126// Correct, signal after unlock
127class Waiter5 : public Waiter
128{
129 public:
130  void wait() override
131  {
132    pthread_mutex_lock(&mutex_);
133    while (!signaled_)
134    {
135      pthread_cond_wait(&cond_, &mutex_);
136    }
137    pthread_mutex_unlock(&mutex_);
138  }
139
140  void signal() override
141  {
142    pthread_mutex_lock(&mutex_);
143    signaled_ = true;
144    pthread_mutex_unlock(&mutex_);
145    pthread_cond_signal(&cond_);
146  }
147
148 private:
149  bool signaled_ = false;
150};
151
152// Note: version 4 is as efficient as version 5 because of "wait morphing"
153
154// Version 6: signal before set boolean flag
155// Correct or not?
156class Waiter6 : public Waiter
157{
158 public:
159  void wait() override
160  {
161    pthread_mutex_lock(&mutex_);
162    while (!signaled_)
163    {
164      pthread_cond_wait(&cond_, &mutex_);
165    }
166    pthread_mutex_unlock(&mutex_);
167  }
168
169  void signal() override
170  {
171    pthread_mutex_lock(&mutex_);
172    pthread_cond_signal(&cond_);
173    signaled_ = true;
174    pthread_mutex_unlock(&mutex_);
175  }
176
177 private:
178  bool signaled_ = false;
179};
180
181// Version 8: modify signaled_ without lock
182// Incorrect, data-race and could lose signal
183class Waiter8 : public Waiter
184{
185 public:
186  void wait() override
187  {
188    pthread_mutex_lock(&mutex_);
189    while (!signaled_)
190    {
191      pthread_cond_wait(&cond_, &mutex_);
192    }
193    pthread_mutex_unlock(&mutex_);
194  }
195
196  void signal() override
197  {
198    signaled_ = true;
199    pthread_cond_signal(&cond_);
200  }
201
202 private:
203  bool signaled_ = false;
204};
205
206// Version 7: broadcast to wakeup multiple waiting threads
207// Probably the best version among above.
208class Waiter7 : public Waiter
209{
210 public:
211  void wait() override
212  {
213    pthread_mutex_lock(&mutex_);
214    while (!signaled_)
215    {
216      pthread_cond_wait(&cond_, &mutex_);
217    }
218    pthread_mutex_unlock(&mutex_);
219  }
220
221  void signal() override // Sorry, bad name in base class, poor OOP
222  {
223    broadcast();
224  }
225
226  void broadcast()
227  {
228    pthread_mutex_lock(&mutex_);
229    pthread_cond_broadcast(&cond_);
230    signaled_ = true;
231    pthread_mutex_unlock(&mutex_);
232  }
233
234 private:
235  bool signaled_ = false;
236};
237
238// Same as version 7, with muduo library
239class WaiterInMuduo : boost::noncopyable
240{
241 public:
242  WaiterInMuduo()
243    : cond_(mutex_)
244  {
245  }
246
247  void wait()
248  {
249    muduo::MutexLockGuard lock(mutex_);
250    while (!signaled_)
251    {
252      cond_.wait();
253    }
254  }
255
256  void broadcast()
257  {
258    muduo::MutexLockGuard lock(mutex_);
259    signaled_ = true;
260    cond_.notifyAll();
261  }
262
263 private:
264  muduo::MutexLock mutex_;
265  muduo::Condition cond_;
266  bool signaled_ = false;
267};
268
269void initAndRefresh(Waiter* waiter)
270{
271  printf("init: running\n");
272
273  struct timespec ts = { 0, 500*1000*1000 };
274  nanosleep(&ts, NULL);  // initialization takes 500ms
275
276  waiter->signal();
277  printf("init: signaled\n");
278}
279
280void runServer(Waiter* waiter, int sec)
281{
282  muduo::Thread initThread(boost::bind(initAndRefresh, waiter));
283  initThread.start();
284  printf("main: init thread started\n");
285
286  struct timespec ts = { sec, 0 };
287  nanosleep(&ts, NULL); // do some work before calling wait()
288
289  printf("main: waiting\n");
290  waiter->wait();
291  printf("main: done\n");
292}
293
294void sigalarm(int)
295{
296  write(1, "\nFAILED\n", 8);
297  exit(1);
298}
299
300int main(int argc, char* argv[])
301{
302  signal(SIGALRM, sigalarm);
303  alarm(5);
304
305  Waiter1 w1;
306  Waiter2 w2;
307  Waiter3 w3;
308  Waiter4 w4;
309  Waiter5 w5;
310  Waiter6 w6;
311  Waiter7 w7;
312  Waiter8 w8;
313  WaiterInMuduo waiterInMuduo;
314
315  Waiter* waiters[] = { NULL, &w1, &w2, &w3, &w4, &w5, &w6, &w7, &w8 };
316  int whichWaiter = argc > 1 ? atoi(argv[1]) : 5;
317  if (!(whichWaiter > 0 && whichWaiter <= 8))
318  {
319    printf("Unknown waiter, must between 1 and 8, inclusive.\n");
320    return 1;
321  }
322  Waiter* waiter = waiters[whichWaiter];
323
324  int sec = 0;
325  printf("test 1: wait() before signal().\n");
326  runServer(waiter, sec);
327
328  printf("\ntest 2: wait() after signal().\n");
329  sec = 1;
330  runServer(waiter, sec);
331
332  printf("\nPASSED!\n");
333}
334