]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | Copyright Oliver Kowalke 2009. | |
3 | Distributed under the Boost Software License, Version 1.0. | |
4 | (See accompanying file LICENSE_1_0.txt or copy at | |
5 | http://www.boost.org/LICENSE_1_0.txt | |
6 | ] | |
7 | ||
8 | [section:symmetric Symmetric coroutine] | |
9 | ||
10 | In contrast to asymmetric coroutines, where the relationship between caller and | |
11 | callee is fixed, symmetric coroutines are able to transfer execution control | |
12 | to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required | |
13 | to return to its direct caller. | |
14 | ||
15 | ||
16 | [heading __call_coro__] | |
17 | __call_coro__ starts a symmetric coroutine and transfers its parameter to its | |
18 | __coro_fn__. | |
19 | The template parameter defines the transferred parameter type. | |
20 | The constructor of __call_coro__ takes a function (__coro_fn__) accepting a | |
21 | reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does | |
22 | not pass the control of execution to __coro_fn__ - instead the first call of | |
23 | __call_coro_op__ synthesizes a __yield_coro__ and passes it as reference to | |
24 | __coro_fn__. | |
25 | ||
26 | The __call_coro__ interface does not contain a ['get()]-function: you can not | |
27 | retrieve values from another execution context with this kind of coroutine | |
28 | object. | |
29 | ||
30 | ||
31 | [heading __yield_coro__] | |
32 | __yield_coro_op__ is used to transfer data and execution control to another | |
33 | context by calling __yield_coro_op__ with another __call_coro__ as first argument. | |
34 | Alternatively, you may transfer control back to the code that called | |
35 | __call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument. | |
36 | ||
37 | The class has only one template parameter defining the transferred parameter | |
38 | type. | |
39 | Data transferred to the coroutine are accessed through __yield_coro_get__. | |
40 | ||
41 | [important __yield_coro__ can only be created by the framework.] | |
42 | ||
43 | std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b) | |
44 | { | |
45 | std::vector<int> c; | |
46 | std::size_t idx_a=0,idx_b=0; | |
47 | boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0; | |
48 | ||
49 | boost::coroutines::symmetric_coroutine<void>::call_type coro_a( | |
50 | [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) { | |
51 | while(idx_a<a.size()) | |
52 | { | |
53 | if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a | |
54 | yield(*other_b); // yield to coroutine coro_b | |
55 | c.push_back(a[idx_a++]); // add element to final array | |
56 | } | |
57 | // add remaining elements of array b | |
58 | while ( idx_b < b.size()) | |
59 | c.push_back( b[idx_b++]); | |
60 | }); | |
61 | ||
62 | boost::coroutines::symmetric_coroutine<void>::call_type coro_b( | |
63 | [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) { | |
64 | while(idx_b<b.size()) | |
65 | { | |
66 | if (a[idx_a]<b[idx_b]) // test if element in array a is less than in array b | |
67 | yield(*other_a); // yield to coroutine coro_a | |
68 | c.push_back(b[idx_b++]); // add element to final array | |
69 | } | |
70 | // add remaining elements of array a | |
71 | while ( idx_a < a.size()) | |
72 | c.push_back( a[idx_a++]); | |
73 | }); | |
74 | ||
75 | ||
76 | other_a = & coro_a; | |
77 | other_b = & coro_b; | |
78 | ||
79 | coro_a(); // enter coroutine-fn of coro_a | |
80 | ||
81 | return c; | |
82 | } | |
83 | ||
84 | std::vector< int > a = {1,5,6,10}; | |
85 | std::vector< int > b = {2,4,7,8,9,13}; | |
86 | std::vector< int > c = merge(a,b); | |
87 | print(a); | |
88 | print(b); | |
89 | print(c); | |
90 | ||
91 | output: | |
92 | a : 1 5 6 10 | |
93 | b : 2 4 7 8 9 13 | |
94 | c : 1 2 4 5 6 7 8 9 10 13 | |
95 | ||
96 | In this example two __call_coro__ are created in the main execution context | |
97 | accepting a lambda function (== __coro_fn__) which merges elements of two | |
98 | sorted arrays into a third array. | |
99 | `coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and | |
100 | testing if the actual element in the other array is less than the element in | |
101 | the local one. If so, the coroutine yields to the other coroutine `coro_b` | |
102 | using `yield(*other_b)`. If the current element of the local array is less | |
103 | than the element of the other array, it is put to the third array. | |
104 | Because the coroutine jumps back to `coro_a()` (returning from this method) | |
105 | after leaving the __coro_fn__, the elements of the other array will appended | |
106 | at the end of the third array if all element of the local array are processed. | |
107 | ||
108 | ||
109 | [heading coroutine-function] | |
110 | The __coro_fn__ returns ['void] and takes __yield_coro__, providing | |
111 | coroutine functionality inside the __coro_fn__, as argument. Using this | |
112 | instance is the only way to transfer data and execution control. | |
113 | __call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but | |
114 | at the first invocation of __call_coro_op__. | |
115 | ||
116 | Unless the template parameter is `void`, the __coro_fn__ of a | |
117 | __call_coro__ can assume that (a) upon initial entry and (b) after every | |
118 | __yield_coro_op__ call, its __yield_coro_get__ has a new value available. | |
119 | ||
120 | However, if the template parameter is a move-only type, | |
121 | __yield_coro_get__ may only be called once before the next __yield_coro_op__ | |
122 | call. | |
123 | ||
124 | [heading passing data from main-context to a symmetric-coroutine] | |
125 | In order to transfer data to a __call_coro__ from the main-context the | |
126 | framework synthesizes a __yield_coro__ associated with the __call_coro__ | |
127 | instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__. | |
128 | The main-context must call __call_coro_op__ in order to transfer each data value | |
129 | into the __coro_fn__. | |
130 | Access to the transferred data value is given by __yield_coro_get__. | |
131 | ||
132 | boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function | |
133 | [&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){ | |
134 | for (;;) { | |
135 | std::cout << yield.get() << " "; | |
136 | yield(); // jump back to starting context | |
137 | } | |
138 | }); | |
139 | ||
140 | coro(1); // transfer {1} to coroutine-function | |
141 | coro(2); // transfer {2} to coroutine-function | |
142 | coro(3); // transfer {3} to coroutine-function | |
143 | coro(4); // transfer {4} to coroutine-function | |
144 | coro(5); // transfer {5} to coroutine-function | |
145 | ||
146 | ||
147 | [heading exceptions] | |
148 | An uncaught exception inside a __call_coro__'s __coro_fn__ will call | |
149 | __terminate__. | |
150 | ||
151 | [important Code executed by coroutine must not prevent the propagation of the | |
152 | __forced_unwind__ exception. Absorbing that exception will cause stack | |
153 | unwinding to fail. Thus, any code that catches all exceptions must re-throw any | |
154 | pending __forced_unwind__ exception.] | |
155 | ||
156 | try { | |
157 | // code that might throw | |
158 | } catch(const boost::coroutines::detail::forced_unwind&) { | |
159 | throw; | |
160 | } catch(...) { | |
161 | // possibly not re-throw pending exception | |
162 | } | |
163 | ||
164 | [important Do not jump from inside a catch block and then re-throw the | |
165 | exception in another execution context.] | |
166 | ||
167 | ||
168 | [heading Stack unwinding] | |
169 | Sometimes it is necessary to unwind the stack of an unfinished coroutine to | |
170 | destroy local stack variables so they can release allocated resources (RAII | |
171 | pattern). The `attributes` argument of the coroutine constructor indicates | |
172 | whether the destructor should unwind the stack (stack is unwound by default). | |
173 | ||
174 | Stack unwinding assumes the following preconditions: | |
175 | ||
176 | * The coroutine is not __not_a_coro__ | |
177 | * The coroutine is not complete | |
178 | * The coroutine is not running | |
179 | * The coroutine owns a stack | |
180 | ||
181 | After unwinding, a __coro__ is complete. | |
182 | ||
183 | struct X { | |
184 | X(){ | |
185 | std::cout<<"X()"<<std::endl; | |
186 | } | |
187 | ||
188 | ~X(){ | |
189 | std::cout<<"~X()"<<std::endl; | |
190 | } | |
191 | }; | |
192 | ||
193 | boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...); | |
194 | ||
195 | { | |
196 | boost::coroutines::symmetric_coroutine<void>::call_type coro( | |
197 | [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){ | |
198 | X x; | |
199 | std::cout<<"fn()"<<std::endl; | |
200 | // transfer execution control to other coroutine | |
201 | yield( other_coro, 7); | |
202 | }); | |
203 | ||
204 | coro(); | |
205 | ||
206 | std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n"; | |
207 | } | |
208 | ||
209 | output: | |
210 | X() | |
211 | fn() | |
212 | coro is complete: false | |
213 | ~X() | |
214 | ||
215 | ||
216 | [heading Exit a __coro_fn__] | |
217 | __coro_fn__ is exited with a simple return statement. This jumps back to the | |
218 | calling __call_coro_op__ at the start of symmetric coroutine chain. That is, | |
219 | symmetric coroutines do not have a strong, fixed relationship to the caller as | |
220 | do asymmetric coroutines. The __call_coro__ becomes complete, e.g. | |
221 | __call_coro_bool__ will return `false`. | |
222 | ||
223 | [important After returning from __coro_fn__ the __coro__ is complete (can not be | |
224 | resumed with __call_coro_op__).] | |
225 | ||
226 | ||
227 | [section:symmetric_coro Class `symmetric_coroutine<>::call_type`] | |
228 | ||
229 | #include <boost/coroutine/symmetric_coroutine.hpp> | |
230 | ||
231 | template< typename Arg > | |
232 | class symmetric_coroutine<>::call_type | |
233 | { | |
234 | public: | |
235 | call_type() noexcept; | |
236 | ||
237 | template< typename Fn > | |
238 | call_type( Fn && fn, attributes const& attr = attributes() ); | |
239 | ||
240 | template< typename Fn, typename StackAllocator > | |
241 | call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc); | |
242 | ||
243 | ~call_type(); | |
244 | ||
245 | call_type( call_type const& other)=delete; | |
246 | ||
247 | call_type & operator=( call_type const& other)=delete; | |
248 | ||
249 | call_type( call_type && other) noexcept; | |
250 | ||
251 | call_type & operator=( call_type && other) noexcept; | |
252 | ||
253 | operator unspecified-bool-type() const; | |
254 | ||
255 | bool operator!() const noexcept; | |
256 | ||
257 | void swap( call_type & other) noexcept; | |
258 | ||
259 | call_type & operator()( Arg arg) noexcept; | |
260 | }; | |
261 | ||
262 | template< typename Arg > | |
263 | void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r); | |
264 | ||
265 | [heading `call_type()`] | |
266 | [variablelist | |
267 | [[Effects:] [Creates a coroutine representing __not_a_coro__.]] | |
268 | [[Throws:] [Nothing.]] | |
269 | ] | |
270 | ||
271 | [heading `template< typename Fn > | |
272 | call_type( Fn fn, attributes const& attr)`] | |
273 | [variablelist | |
274 | [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() | |
275 | when ! is_stack_unbounded().]] | |
276 | [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` | |
277 | determines stack clean-up. | |
278 | For allocating/deallocating the stack `stack_alloc` is used.]] | |
279 | ] | |
280 | ||
281 | [heading `template< typename Fn, typename StackAllocator > | |
282 | call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`] | |
283 | [variablelist | |
284 | [[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize() | |
285 | when ! is_stack_unbounded().]] | |
286 | [[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr` | |
287 | determines stack clean-up. | |
288 | For allocating/deallocating the stack `stack_alloc` is used.]] | |
289 | ] | |
290 | ||
291 | [heading `~call_type()`] | |
292 | [variablelist | |
293 | [[Effects:] [Destroys the context and deallocates the stack.]] | |
294 | ] | |
295 | ||
296 | [heading `call_type( call_type && other)`] | |
297 | [variablelist | |
298 | [[Effects:] [Moves the internal data of `other` to `*this`. | |
299 | `other` becomes __not_a_coro__.]] | |
300 | [[Throws:] [Nothing.]] | |
301 | ] | |
302 | ||
303 | [heading `call_type & operator=( call_type && other)`] | |
304 | [variablelist | |
305 | [[Effects:] [Destroys the internal data of `*this` and moves the | |
306 | internal data of `other` to `*this`. `other` becomes __not_a_coro__.]] | |
307 | [[Throws:] [Nothing.]] | |
308 | ] | |
309 | ||
310 | [heading `operator unspecified-bool-type() const`] | |
311 | [variablelist | |
312 | [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function | |
313 | has returned (completed), the function returns `false`. Otherwise `true`.]] | |
314 | [[Throws:] [Nothing.]] | |
315 | ] | |
316 | ||
317 | [heading `bool operator!() const`] | |
318 | [variablelist | |
319 | [[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function | |
320 | has returned (completed), the function returns `true`. Otherwise `false`.]] | |
321 | [[Throws:] [Nothing.]] | |
322 | ] | |
323 | ||
324 | [heading `void swap( call_type & other)`] | |
325 | [variablelist | |
326 | [[Effects:] [Swaps the internal data from `*this` with the values | |
327 | of `other`.]] | |
328 | [[Throws:] [Nothing.]] | |
329 | ] | |
330 | ||
331 | [heading `call_type & operator()(Arg arg)`] | |
332 | ||
333 | symmetric_coroutine::call_type& coroutine<Arg,StackAllocator>::call_type::operator()(Arg); | |
334 | symmetric_coroutine::call_type& coroutine<Arg&,StackAllocator>::call_type::operator()(Arg&); | |
335 | symmetric_coroutine::call_type& coroutine<void,StackAllocator>::call_type::operator()(); | |
336 | ||
337 | [variablelist | |
338 | [[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]] | |
339 | [[Effects:] [Execution control is transferred to __coro_fn__ and the argument | |
340 | `arg` is passed to the coroutine-function.]] | |
341 | [[Throws:] [Nothing.]] | |
342 | ] | |
343 | ||
344 | [heading Non-member function `swap()`] | |
345 | ||
346 | template< typename Arg > | |
347 | void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r); | |
348 | ||
349 | [variablelist | |
350 | [[Effects:] [As if 'l.swap( r)'.]] | |
351 | ] | |
352 | ||
353 | [endsect] | |
354 | ||
355 | ||
356 | ||
357 | [section:yield_coro Class `symmetric_coroutine<>::yield_type`] | |
358 | ||
359 | #include <boost/coroutine/symmetric_coroutine.hpp> | |
360 | ||
361 | template< typename R > | |
362 | class symmetric_coroutine<>::yield_type | |
363 | { | |
364 | public: | |
365 | yield_type() noexcept; | |
366 | ||
367 | yield_type( yield_type const& other)=delete; | |
368 | ||
369 | yield_type & operator=( yield_type const& other)=delete; | |
370 | ||
371 | yield_type( yield_type && other) noexcept; | |
372 | ||
373 | yield_type & operator=( yield_type && other) noexcept; | |
374 | ||
375 | void swap( yield_type & other) noexcept; | |
376 | ||
377 | operator unspecified-bool-type() const; | |
378 | ||
379 | bool operator!() const noexcept; | |
380 | ||
381 | yield_type & operator()(); | |
382 | ||
383 | template< typename X > | |
384 | yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x); | |
385 | ||
386 | template< typename X > | |
387 | yield_type & operator()( symmetric_coroutine< X >::call_type & other); | |
388 | ||
389 | R get() const; | |
390 | }; | |
391 | ||
392 | [heading `operator unspecified-bool-type() const`] | |
393 | [variablelist | |
394 | [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`. | |
395 | Otherwise `true`.]] | |
396 | [[Throws:] [Nothing.]] | |
397 | ] | |
398 | ||
399 | [heading `bool operator!() const`] | |
400 | [variablelist | |
401 | [[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`. | |
402 | Otherwise `false`.]] | |
403 | [[Throws:] [Nothing.]] | |
404 | ] | |
405 | ||
406 | [heading `yield_type & operator()()`] | |
407 | ||
408 | yield_type & operator()(); | |
409 | template< typename X > | |
410 | yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x); | |
411 | template<> | |
412 | yield_type & operator()( symmetric_coroutine< void >::call_type & other); | |
413 | ||
414 | [variablelist | |
415 | [[Preconditions:] [`*this` is not a __not_a_coro__.]] | |
416 | [[Effects:] [The first function transfers execution control back to the starting point, | |
417 | e.g. invocation of __call_coro_op__. The last two functions transfer the execution control | |
418 | to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]] | |
419 | [[Throws:] [__forced_unwind__]] | |
420 | ] | |
421 | ||
422 | [heading `R get()`] | |
423 | ||
424 | R symmetric_coroutine<R>::yield_type::get(); | |
425 | R& symmetric_coroutine<R&>::yield_type::get(); | |
426 | void symmetric_coroutine<void>yield_type::get()=delete; | |
427 | ||
428 | [variablelist | |
429 | [[Preconditions:] [`*this` is not a __not_a_coro__.]] | |
430 | [[Returns:] [Returns data transferred from coroutine-function via | |
431 | __call_coro_op__.]] | |
432 | [[Throws:] [`invalid_result`]] | |
433 | ] | |
434 | ||
435 | [endsect] | |
436 | ||
437 | ||
438 | ||
439 | [endsect] |