1 | <?xml version="1.0" encoding="utf-8"?> |
---|
2 | <!DOCTYPE library PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" |
---|
3 | "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd" [ |
---|
4 | <!ENTITY % threads.entities SYSTEM "entities.xml"> |
---|
5 | %threads.entities; |
---|
6 | ]> |
---|
7 | <section id="threads.rationale" last-revision="$Date: 2004/07/17 04:33:59 $"> |
---|
8 | <title>Rationale</title> |
---|
9 | <para>This page explains the rationale behind various design decisions in the |
---|
10 | &Boost.Threads; library. Having the rationale documented here should explain |
---|
11 | how we arrived at the current design as well as prevent future rehashing of |
---|
12 | discussions and thought processes that have already occurred. It can also give |
---|
13 | users a lot of insight into the design process required for this |
---|
14 | library.</para> |
---|
15 | <section id="threads.rationale.Boost.Threads"> |
---|
16 | <title>Rationale for the Creation of &Boost.Threads;</title> |
---|
17 | <para>Processes often have a degree of "potential parallelism" and it can |
---|
18 | often be more intuitive to design systems with this in mind. Further, these |
---|
19 | parallel processes can result in more responsive programs. The benefits for |
---|
20 | multithreaded programming are quite well known to most modern programmers, |
---|
21 | yet the C++ language doesn't directly support this concept.</para> |
---|
22 | <para>Many platforms support multithreaded programming despite the fact that |
---|
23 | the language doesn't support it. They do this through external libraries, |
---|
24 | which are, unfortunately, platform specific. POSIX has tried to address this |
---|
25 | problem through the standardization of a "pthread" library. However, this is |
---|
26 | a standard only on POSIX platforms, so its portability is limited.</para> |
---|
27 | <para>Another problem with POSIX and other platform specific thread |
---|
28 | libraries is that they are almost universally C based libraries. This leaves |
---|
29 | several C++ specific issues unresolved, such as what happens when an |
---|
30 | exception is thrown in a thread. Further, there are some C++ concepts, such |
---|
31 | as destructors, that can make usage much easier than what's available in a C |
---|
32 | library.</para> |
---|
33 | <para>What's truly needed is C++ language support for threads. However, the |
---|
34 | C++ standards committee needs existing practice or a good proposal as a |
---|
35 | starting point for adding this to the standard.</para> |
---|
36 | <para>The &Boost.Threads; library was developed to provide a C++ developer |
---|
37 | with a portable interface for writing multithreaded programs on numerous |
---|
38 | platforms. There's a hope that the library can be the basis for a more |
---|
39 | detailed proposal for the C++ standards committee to consider for inclusion |
---|
40 | in the next C++ standard.</para> |
---|
41 | </section> |
---|
42 | <section id="threads.rationale.primitives"> |
---|
43 | <title>Rationale for the Low Level Primitives Supported in &Boost.Threads;</title> |
---|
44 | <para>The &Boost.Threads; library supplies a set of low level primitives for |
---|
45 | writing multithreaded programs, such as mutexes and condition variables. In |
---|
46 | fact, the first release of &Boost.Threads; supports only these low level |
---|
47 | primitives. However, computer science research has shown that use of these |
---|
48 | primitives is difficult since it's difficult to mathematically prove that a |
---|
49 | usage pattern is correct, meaning it doesn't result in race conditions or |
---|
50 | deadlocks. There are several algebras (such as CSP, CCS and Join calculus) |
---|
51 | that have been developed to help write provably correct parallel |
---|
52 | processes. In order to prove the correctness these processes must be coded |
---|
53 | using higher level abstractions. So why does &Boost.Threads; support the |
---|
54 | lower level concepts?</para> |
---|
55 | <para>The reason is simple: the higher level concepts need to be implemented |
---|
56 | using at least some of the lower level concepts. So having portable lower |
---|
57 | level concepts makes it easier to develop the higher level concepts and will |
---|
58 | allow researchers to experiment with various techniques.</para> |
---|
59 | <para>Beyond this theoretical application of higher level concepts, however, |
---|
60 | the fact remains that many multithreaded programs are written using only the |
---|
61 | lower level concepts, so they are useful in and of themselves, even if it's |
---|
62 | hard to prove that their usage is correct. Since many users will be familiar |
---|
63 | with these lower level concepts but unfamiliar with any of the higher |
---|
64 | level concepts, supporting the lower level concepts provides |
---|
65 | greater accessibility.</para> |
---|
66 | </section> |
---|
67 | <section id="threads.rationale.locks"> |
---|
68 | <title>Rationale for the Lock Design</title> |
---|
69 | <para>Programmers who are used to multithreaded programming issues will |
---|
70 | quickly note that the &Boost.Threads; design for mutex lock concepts is not |
---|
71 | <link linkend="threads.glossary.thread-safe">thread-safe</link> (this is |
---|
72 | clearly documented as well). At first this may seem like a serious design |
---|
73 | flaw. Why have a multithreading primitive that's not thread-safe |
---|
74 | itself?</para> |
---|
75 | <para>A lock object is not a synchronization primitive. A lock object's sole |
---|
76 | responsibility is to ensure that a mutex is both locked and unlocked in a |
---|
77 | manner that won't result in the common error of locking a mutex and then |
---|
78 | forgetting to unlock it. This means that instances of a lock object are only |
---|
79 | going to be created, at least in theory, within block scope and won't be |
---|
80 | shared between threads. Only the mutex objects will be created outside of |
---|
81 | block scope and/or shared between threads. Though it's possible to create a |
---|
82 | lock object outside of block scope and to share it between threads, to do so |
---|
83 | would not be a typical usage (in fact, to do so would likely be an |
---|
84 | error). Nor are there any cases when such usage would be required.</para> |
---|
85 | <para>Lock objects must maintain some state information. In order to allow a |
---|
86 | program to determine if a try_lock or timed_lock was successful the lock |
---|
87 | object must retain state indicating the success or failure of the call made |
---|
88 | in its constructor. If a lock object were to have such state and remain |
---|
89 | thread-safe it would need to synchronize access to the state information |
---|
90 | which would result in roughly doubling the time of most operations. Worse, |
---|
91 | since checking the state can occur only by a call after construction, we'd |
---|
92 | have a race condition if the lock object were shared between threads.</para> |
---|
93 | <para>So, to avoid the overhead of synchronizing access to the state |
---|
94 | information and to avoid the race condition, the &Boost.Threads; library |
---|
95 | simply does nothing to make lock objects thread-safe. Instead, sharing a |
---|
96 | lock object between threads results in undefined behavior. Since the only |
---|
97 | proper usage of lock objects is within block scope this isn't a problem, and |
---|
98 | so long as the lock object is properly used there's no danger of any |
---|
99 | multithreading issues.</para> |
---|
100 | </section> |
---|
101 | <section id="threads.rationale.non-copyable"> |
---|
102 | <title>Rationale for NonCopyable Thread Type</title> |
---|
103 | <para>Programmers who are used to C libraries for multithreaded programming |
---|
104 | are likely to wonder why &Boost.Threads; uses a noncopyable design for |
---|
105 | <classname>boost::thread</classname>. After all, the C thread types are |
---|
106 | copyable, and you often have a need for copying them within user |
---|
107 | code. However, careful comparison of C designs to C++ designs shows a flaw |
---|
108 | in this logic.</para> |
---|
109 | <para>All C types are copyable. It is, in fact, not possible to make a |
---|
110 | noncopyable type in C. For this reason types that represent system resources |
---|
111 | in C are often designed to behave very similarly to a pointer to dynamic |
---|
112 | memory. There's an API for acquiring the resource and an API for releasing |
---|
113 | the resource. For memory we have pointers as the type and alloc/free for |
---|
114 | the acquisition and release APIs. For files we have FILE* as the type and |
---|
115 | fopen/fclose for the acquisition and release APIs. You can freely copy |
---|
116 | instances of the types but must manually manage the lifetime of the actual |
---|
117 | resource through the acquisition and release APIs.</para> |
---|
118 | <para>C++ designs recognize that the acquisition and release APIs are error |
---|
119 | prone and try to eliminate possible errors by acquiring the resource in the |
---|
120 | constructor and releasing it in the destructor. The best example of such a |
---|
121 | design is the std::iostream set of classes which can represent the same |
---|
122 | resource as the FILE* type in C. A file is opened in the std::fstream's |
---|
123 | constructor and closed in its destructor. However, if an iostream were |
---|
124 | copyable it could lead to a file being closed twice, an obvious error, so |
---|
125 | the std::iostream types are noncopyable by design. This is the same design |
---|
126 | used by boost::thread, which is a simple and easy to understand design |
---|
127 | that's consistent with other C++ standard types.</para> |
---|
128 | <para>During the design of boost::thread it was pointed out that it would be |
---|
129 | possible to allow it to be a copyable type if some form of "reference |
---|
130 | management" were used, such as ref-counting or ref-lists, and many argued |
---|
131 | for a boost::thread_ref design instead. The reasoning was that copying |
---|
132 | "thread" objects was a typical need in the C libraries, and so presumably |
---|
133 | would be in the C++ libraries as well. It was also thought that |
---|
134 | implementations could provide more efficient reference management than |
---|
135 | wrappers (such as boost::shared_ptr) around a noncopyable thread |
---|
136 | concept. Analysis of whether or not these arguments would hold true doesn't |
---|
137 | appear to bear them out. To illustrate the analysis we'll first provide |
---|
138 | pseudo-code illustrating the six typical usage patterns of a thread |
---|
139 | object.</para> |
---|
140 | <section id="threads.rationale.non-copyable.simple"> |
---|
141 | <title>1. Use case: Simple creation of a thread.</title> |
---|
142 | <programlisting> |
---|
143 | void foo() |
---|
144 | { |
---|
145 | create_thread(&bar); |
---|
146 | } |
---|
147 | </programlisting> |
---|
148 | </section> |
---|
149 | <section id="threads.rationale.non-copyable.joined"> |
---|
150 | <title>2. Use case: Creation of a thread that's later joined.</title> |
---|
151 | <programlisting> |
---|
152 | void foo() |
---|
153 | { |
---|
154 | thread = create_thread(&bar); |
---|
155 | join(thread); |
---|
156 | } |
---|
157 | </programlisting> |
---|
158 | </section> |
---|
159 | <section id="threads.rationale.non-copyable.loop"> |
---|
160 | <title>3. Use case: Simple creation of several threads in a loop.</title> |
---|
161 | <programlisting> |
---|
162 | void foo() |
---|
163 | { |
---|
164 | for (int i=0; i<NUM_THREADS; ++i) |
---|
165 | create_thread(&bar); |
---|
166 | } |
---|
167 | </programlisting> |
---|
168 | </section> |
---|
169 | <section id="threads.rationale.non-copyable.loop-join"> |
---|
170 | <title>4. Use case: Creation of several threads in a loop which are later joined.</title> |
---|
171 | <programlisting> |
---|
172 | void foo() |
---|
173 | { |
---|
174 | for (int i=0; i<NUM_THREADS; ++i) |
---|
175 | threads[i] = create_thread(&bar); |
---|
176 | for (int i=0; i<NUM_THREADS; ++i) |
---|
177 | threads[i].join(); |
---|
178 | } |
---|
179 | </programlisting> |
---|
180 | </section> |
---|
181 | <section id="threads.rationale.non-copyable.pass"> |
---|
182 | <title>5. Use case: Creation of a thread whose ownership is passed to another object/method.</title> |
---|
183 | <programlisting> |
---|
184 | void foo() |
---|
185 | { |
---|
186 | thread = create_thread(&bar); |
---|
187 | manager.owns(thread); |
---|
188 | } |
---|
189 | </programlisting> |
---|
190 | </section> |
---|
191 | <section id="threads.rationale.non-copyable.shared"> |
---|
192 | <title>6. Use case: Creation of a thread whose ownership is shared between multiple |
---|
193 | objects.</title> |
---|
194 | <programlisting> |
---|
195 | void foo() |
---|
196 | { |
---|
197 | thread = create_thread(&bar); |
---|
198 | manager1.add(thread); |
---|
199 | manager2.add(thread); |
---|
200 | } |
---|
201 | </programlisting> |
---|
202 | </section> |
---|
203 | <para>Of these usage patterns there's only one that requires reference |
---|
204 | management (number 6). Hopefully it's fairly obvious that this usage pattern |
---|
205 | simply won't occur as often as the other usage patterns. So there really |
---|
206 | isn't a "typical need" for a thread concept, though there is some |
---|
207 | need.</para> |
---|
208 | <para>Since the need isn't typical we must use different criteria for |
---|
209 | deciding on either a thread_ref or thread design. Possible criteria include |
---|
210 | ease of use and performance. So let's analyze both of these |
---|
211 | carefully.</para> |
---|
212 | <para>With ease of use we can look at existing experience. The standard C++ |
---|
213 | objects that represent a system resource, such as std::iostream, are |
---|
214 | noncopyable, so we know that C++ programmers must at least be experienced |
---|
215 | with this design. Most C++ developers are also used to smart pointers such |
---|
216 | as boost::shared_ptr, so we know they can at least adapt to a thread_ref |
---|
217 | concept with little effort. So existing experience isn't going to lead us to |
---|
218 | a choice.</para> |
---|
219 | <para>The other thing we can look at is how difficult it is to use both |
---|
220 | types for the six usage patterns above. If we find it overly difficult to |
---|
221 | use a concept for any of the usage patterns there would be a good argument |
---|
222 | for choosing the other design. So we'll code all six usage patterns using |
---|
223 | both designs.</para> |
---|
224 | <section id="threads.rationale_comparison.non-copyable.simple"> |
---|
225 | <title>1. Comparison: simple creation of a thread.</title> |
---|
226 | <programlisting> |
---|
227 | void foo() |
---|
228 | { |
---|
229 | thread thrd(&bar); |
---|
230 | } |
---|
231 | void foo() |
---|
232 | { |
---|
233 | thread_ref thrd = create_thread(&bar); |
---|
234 | } |
---|
235 | </programlisting> |
---|
236 | </section> |
---|
237 | <section id="threads.rationale_comparison.non-copyable.joined"> |
---|
238 | <title>2. Comparison: creation of a thread that's later joined.</title> |
---|
239 | <programlisting> |
---|
240 | void foo() |
---|
241 | { |
---|
242 | thread thrd(&bar); |
---|
243 | thrd.join(); |
---|
244 | } |
---|
245 | void foo() |
---|
246 | { |
---|
247 | thread_ref thrd = |
---|
248 | create_thread(&bar);thrd->join(); |
---|
249 | } |
---|
250 | </programlisting> |
---|
251 | </section> |
---|
252 | <section id="threads.rationale_comparison.non-copyable.loop"> |
---|
253 | <title>3. Comparison: simple creation of several threads in a loop.</title> |
---|
254 | <programlisting> |
---|
255 | void foo() |
---|
256 | { |
---|
257 | for (int i=0; i<NUM_THREADS; ++i) |
---|
258 | thread thrd(&bar); |
---|
259 | } |
---|
260 | void foo() |
---|
261 | { |
---|
262 | for (int i=0; i<NUM_THREADS; ++i) |
---|
263 | thread_ref thrd = create_thread(&bar); |
---|
264 | } |
---|
265 | </programlisting> |
---|
266 | </section> |
---|
267 | <section id="threads.rationale_comparison.non-copyable.loop-join"> |
---|
268 | <title>4. Comparison: creation of several threads in a loop which are later joined.</title> |
---|
269 | <programlisting> |
---|
270 | void foo() |
---|
271 | { |
---|
272 | std::auto_ptr<thread> threads[NUM_THREADS]; |
---|
273 | for (int i=0; i<NUM_THREADS; ++i) |
---|
274 | threads[i] = std::auto_ptr<thread>(new thread(&bar)); |
---|
275 | for (int i= 0; i<NUM_THREADS; |
---|
276 | ++i)threads[i]->join(); |
---|
277 | } |
---|
278 | void foo() |
---|
279 | { |
---|
280 | thread_ref threads[NUM_THREADS]; |
---|
281 | for (int i=0; i<NUM_THREADS; ++i) |
---|
282 | threads[i] = create_thread(&bar); |
---|
283 | for (int i= 0; i<NUM_THREADS; |
---|
284 | ++i)threads[i]->join(); |
---|
285 | } |
---|
286 | </programlisting> |
---|
287 | </section> |
---|
288 | <section id="threads.rationale_comparison.non-copyable.pass"> |
---|
289 | <title>5. Comparison: creation of a thread whose ownership is passed to another object/method.</title> |
---|
290 | <programlisting> |
---|
291 | void foo() |
---|
292 | { |
---|
293 | thread thrd* = new thread(&bar); |
---|
294 | manager.owns(thread); |
---|
295 | } |
---|
296 | void foo() |
---|
297 | { |
---|
298 | thread_ref thrd = create_thread(&bar); |
---|
299 | manager.owns(thrd); |
---|
300 | } |
---|
301 | </programlisting> |
---|
302 | </section> |
---|
303 | <section id="threads.rationale_comparison.non-copyable.shared"> |
---|
304 | <title>6. Comparison: creation of a thread whose ownership is shared |
---|
305 | between multiple objects.</title> |
---|
306 | <programlisting> |
---|
307 | void foo() |
---|
308 | { |
---|
309 | boost::shared_ptr<thread> thrd(new thread(&bar)); |
---|
310 | manager1.add(thrd); |
---|
311 | manager2.add(thrd); |
---|
312 | } |
---|
313 | void foo() |
---|
314 | { |
---|
315 | thread_ref thrd = create_thread(&bar); |
---|
316 | manager1.add(thrd); |
---|
317 | manager2.add(thrd); |
---|
318 | } |
---|
319 | </programlisting> |
---|
320 | </section> |
---|
321 | <para>This shows the usage patterns being nearly identical in complexity for |
---|
322 | both designs. The only actual added complexity occurs because of the use of |
---|
323 | operator new in |
---|
324 | <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link>, |
---|
325 | <link linkend="threads.rationale_comparison.non-copyable.pass">(5)</link>, and |
---|
326 | <link linkend="threads.rationale_comparison.non-copyable.shared">(6)</link>; |
---|
327 | and the use of std::auto_ptr and boost::shared_ptr in |
---|
328 | <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link> and |
---|
329 | <link linkend="threads.rationale_comparison.non-copyable.shared">(6)</link> |
---|
330 | respectively. However, that's not really |
---|
331 | much added complexity, and C++ programmers are used to using these idioms |
---|
332 | anyway. Some may dislike the presence of operator new in user code, but |
---|
333 | this can be eliminated by proper design of higher level concepts, such as |
---|
334 | the boost::thread_group class that simplifies example |
---|
335 | <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link> |
---|
336 | down to:</para> |
---|
337 | <programlisting> |
---|
338 | void foo() |
---|
339 | { |
---|
340 | thread_group threads; |
---|
341 | for (int i=0; i<NUM_THREADS; ++i) |
---|
342 | threads.create_thread(&bar); |
---|
343 | threads.join_all(); |
---|
344 | } |
---|
345 | </programlisting> |
---|
346 | <para>So ease of use is really a wash and not much help in picking a |
---|
347 | design.</para> |
---|
348 | <para>So what about performance? Looking at the above code examples, |
---|
349 | we can analyze the theoretical impact to performance that both designs |
---|
350 | have. For <link linkend="threads.rationale_comparison.non-copyable.simple">(1)</link> |
---|
351 | we can see that platforms that don't have a ref-counted native |
---|
352 | thread type (POSIX, for instance) will be impacted by a thread_ref |
---|
353 | design. Even if the native thread type is ref-counted there may be an impact |
---|
354 | if more state information has to be maintained for concepts foreign to the |
---|
355 | native API, such as clean up stacks for Win32 implementations. |
---|
356 | For <link linkend="threads.rationale_comparison.non-copyable.joined">(2)</link> |
---|
357 | and <link linkend="threads.rationale_comparison.non-copyable.loop">(3)</link> |
---|
358 | the performance impact will be identical to |
---|
359 | <link linkend="threads.rationale_comparison.non-copyable.simple">(1)</link>. |
---|
360 | For <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link> |
---|
361 | things get a little more interesting and we find that theoretically at least |
---|
362 | the thread_ref may perform faster since the thread design requires dynamic |
---|
363 | memory allocation/deallocation. However, in practice there may be dynamic |
---|
364 | allocation for the thread_ref design as well, it will just be hidden from |
---|
365 | the user. As long as the implementation has to do dynamic allocations the |
---|
366 | thread_ref loses again because of the reference management. For |
---|
367 | <link linkend="threads.rationale_comparison.non-copyable.pass">(5)</link> we see |
---|
368 | the same impact as we do for |
---|
369 | <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link>. |
---|
370 | For <link linkend="threads.rationale_comparison.non-copyable.shared">(6)</link> |
---|
371 | we still have a possible impact to |
---|
372 | the thread design because of dynamic allocation but thread_ref no longer |
---|
373 | suffers because of its reference management, and in fact, theoretically at |
---|
374 | least, the thread_ref may do a better job of managing the references. All of |
---|
375 | this indicates that thread wins for |
---|
376 | <link linkend="threads.rationale_comparison.non-copyable.simple">(1)</link>, |
---|
377 | <link linkend="threads.rationale_comparison.non-copyable.joined">(2)</link> and |
---|
378 | <link linkend="threads.rationale_comparison.non-copyable.loop">(3)</link>; with |
---|
379 | <link linkend="threads.rationale_comparison.non-copyable.loop-join">(4)</link> |
---|
380 | and <link linkend="threads.rationale_comparison.non-copyable.pass">(5)</link> the |
---|
381 | winner depending on the implementation and the platform but with the thread design |
---|
382 | probably having a better chance; and with |
---|
383 | <link linkend="threads.rationale_comparison.non-copyable.shared">(6)</link> |
---|
384 | it will again depend on the |
---|
385 | implementation and platform but this time we favor thread_ref |
---|
386 | slightly. Given all of this it's a narrow margin, but the thread design |
---|
387 | prevails.</para> |
---|
388 | <para>Given this analysis, and the fact that noncopyable objects for system |
---|
389 | resources are the normal designs that C++ programmers are used to dealing |
---|
390 | with, the &Boost.Threads; library has gone with a noncopyable design.</para> |
---|
391 | </section> |
---|
392 | <section id="threads.rationale.events"> |
---|
393 | <title>Rationale for not providing <emphasis>Event Variables</emphasis></title> |
---|
394 | <para><emphasis>Event variables</emphasis> are simply far too |
---|
395 | error-prone. <classname>boost::condition</classname> variables are a much safer |
---|
396 | alternative. [Note that Graphical User Interface <emphasis>events</emphasis> are |
---|
397 | a different concept, and are not what is being discussed here.]</para> |
---|
398 | <para>Event variables were one of the first synchronization primitives. They |
---|
399 | are still used today, for example, in the native Windows multithreading |
---|
400 | API. Yet both respected computer science researchers and experienced |
---|
401 | multithreading practitioners believe event variables are so inherently |
---|
402 | error-prone that they should never be used, and thus should not be part of a |
---|
403 | multithreading library.</para> |
---|
404 | <para>Per Brinch Hansen &cite.Hansen73; analyzed event variables in some |
---|
405 | detail, pointing out [emphasis his] that "<emphasis>event operations force |
---|
406 | the programmer to be aware of the relative speeds of the sending and |
---|
407 | receiving processes</emphasis>". His summary:</para> |
---|
408 | <blockquote> |
---|
409 | <para>We must therefore conclude that event variables of the previous type |
---|
410 | are impractical for system design. <emphasis>The effect of an interaction |
---|
411 | between two processes must be independent of the speed at which it is |
---|
412 | carried out.</emphasis></para> |
---|
413 | </blockquote> |
---|
414 | <para>Experienced programmers using the Windows platform today report that |
---|
415 | event variables are a continuing source of errors, even after previous bad |
---|
416 | experiences caused them to be very careful in their use of event |
---|
417 | variables. Overt problems can be avoided, for example, by teaming the event |
---|
418 | variable with a mutex, but that may just convert a <link |
---|
419 | linkend="threads.glossary.race-condition">race condition</link> into another |
---|
420 | problem, such as excessive resource use. One of the most distressing aspects |
---|
421 | of the experience reports is the claim that many defects are latent. That |
---|
422 | is, the programs appear to work correctly, but contain hidden timing |
---|
423 | dependencies which will cause them to fail when environmental factors or |
---|
424 | usage patterns change, altering relative thread timings.</para> |
---|
425 | <para>The decision to exclude event variables from &Boost.Threads; has been |
---|
426 | surprising to some Windows programmers. They have written programs which |
---|
427 | work using event variables, and wonder what the problem is. It seems similar |
---|
428 | to the "goto considered harmful" controversy of 30 years ago. It isn't that |
---|
429 | events, like gotos, can't be made to work, but rather that virtually all |
---|
430 | programs using alternatives will be easier to write, debug, read, maintain, |
---|
431 | and will be less likely to contain latent defects.</para> |
---|
432 | <para>[Rationale provided by Beman Dawes]</para> |
---|
433 | </section> |
---|
434 | </section> |
---|