fiber.qbk 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. [/
  2. Copyright Oliver Kowalke 2016.
  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. [#ff]
  8. [section:ff Context switching with fibers]
  9. [note __fiber__ is the reference implementation of C++ proposal
  10. [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0876r0.pdf P0876R0:
  11. fibers without scheduler].]
  12. A __fiber__ represents the state of the control flow of a program at a given
  13. point in time. Fibers can be suspended and resumed later in order to change the
  14. control flow of a program.
  15. Modern micro-processors are registers machines; the content of processor
  16. registers represent a fiber of the executed program at a given point in time.
  17. Operating systems simulate parallel execution of programs on a single processor
  18. by switching between programs (context switch) by preserving and restoring the
  19. fiber, e.g. the content of all registers.
  20. [heading __fib__]
  21. __fib__ captures the current fiber (the rest of the computation; code after
  22. __fib__) and triggers a context switch. The context switch is achieved by
  23. preserving certain registers (including instruction and stack pointer), defined
  24. by the calling convention of the ABI, of the current fiber and restoring those
  25. registers of the resumed fiber. The control flow of the resumed fiber continues.
  26. The current fiber is suspended and passed as argument to the resumed fiber.
  27. __fib__ expects a __context_fn__ with signature `'fiber(fiber && f)'`. The
  28. parameter `f` represents the current fiber from which this fiber was resumed
  29. (e.g. that has called __fib__).
  30. On return the __context_fn__ of the current fiber has to specify an __fib__ to
  31. which the execution control is transferred after termination of the current
  32. fiber.
  33. If an instance with valid state goes out of scope and the __context_fn__ has not
  34. yet returned, the stack is traversed in order to access the control structure
  35. (address stored at the first stack frame) and fiber's stack is deallocated via
  36. the __stack_allocator__.
  37. [note [link segmented ['Segmented stacks]] are supported by __fib__ using [link
  38. implementation ['ucontext_t]].]
  39. __fib__ represents a __fiber__; it contains the content of preserved registers
  40. and manages the associated stack (allocation/deallocation). __fib__ is a
  41. one-shot fiber - it can be used only once, after calling __resume__ or
  42. __resume_with__ it is invalidated.
  43. __fib__ is only move-constructible and move-assignable.
  44. As a first-class object __fib__ can be applied to and returned from a function,
  45. assigned to a variable or stored in a container.
  46. A fiber is continued by calling `resume()`/`resume_with()`.
  47. [heading Usage]
  48. namespace ctx=boost::context;
  49. int a;
  50. ctx::fiber source{[&a](ctx::fiber&& sink){
  51. a=0;
  52. int b=1;
  53. for(;;){
  54. sink=std::move(sink).resume();
  55. int next=a+b;
  56. a=b;
  57. b=next;
  58. }
  59. return std::move(sink);
  60. }};
  61. for (int j=0;j<10;++j) {
  62. source=std::move(source).resume();
  63. std::cout << a << " ";
  64. }
  65. output:
  66. 0 1 1 2 3 5 8 13 21 34
  67. This simple example demonstrates the basic usage of __fib__ as a ['generator].
  68. The fiber `sink` represents the ['main]-fiber (function `main()`).
  69. `sink` is captured (current-fiber) by invoking __fib__ and passed as parameter
  70. to the lambda.
  71. Because the state is invalidated (one-shot fiber) by each call of __resume__,
  72. the new state of the __fib__, returned by __resume__, needs to be assigned to
  73. `sink` after each call. In order to express the invalidation of the resumed fiber,
  74. the member functions `resume()` and `resume_with()` are rvalue-ref qualified.
  75. Both functions bind only to rvalues. Thus an lvalue fiber must be casted to an
  76. rvalue via `std::move()`.
  77. The lambda that calculates the Fibonacci numbers is executed inside the fiber
  78. represented by `source`. Calculated Fibonacci numbers are transferred between
  79. the two fibers via variable `a` (lambda capture reference).
  80. The locale variables `b` and ` next` remain their values during each context
  81. switch. This is possible due `source` has its own stack and the stack is
  82. exchanged by each context switch.
  83. [heading Parameter passing]
  84. Data can be transferred between two fibers via global pointers, calling wrappers
  85. (like `std::bind`) or lambda captures.
  86. namespace ctx=boost::context;
  87. int i=1;
  88. ctx::fiber f1{[&i](ctx::fiber&& f2){
  89. std::printf("inside f1,i==%d\n",i);
  90. i+=1;
  91. return std::move(f2).resume();
  92. }};
  93. f1=std::move(f1).resume();
  94. std::printf("i==%d\n",i);
  95. output:
  96. inside c1,i==1
  97. i==2
  98. `f1.resume()` enters the lambda in fiber represented by `f1` with lambda capture
  99. reference `i=1`. The expression `f2.resume()` resumes the fiber `f2`. On return
  100. of `f1.resume()`, the variable `i` has the value of `i+1`.
  101. [heading Exception handling]
  102. If the function executed inside a __context_fn__ emits ans exception, the
  103. application is terminated by calling `std::terminate()`. `std::exception_ptr`
  104. can be used to transfer exceptions between different fibers.
  105. [important Do not jump from inside a catch block and then re-throw the exception
  106. in another fiber.]
  107. [#ff_ontop]
  108. [heading Executing function on top of a fiber]
  109. Sometimes it is useful to execute a new function on top of a resumed fiber. For
  110. this purpose __resume_with__ has to be used.
  111. The function passed as argument must accept a rvalue reference to __fib__ and
  112. return `void`.
  113. namespace ctx=boost::context;
  114. int data=0;
  115. ctx::fiber f1{[&data](ctx::fiber&& f2) {
  116. std::cout << "f1: entered first time: " << data << std::endl;
  117. data+=1;
  118. f2=std::move(f2).resume();
  119. std::cout << "f1: entered second time: " << data << std::endl;
  120. data+=1;
  121. f2=std::move(f2).resume();
  122. std::cout << "f1: entered third time: " << data << std::endl;
  123. return std::move(f2);
  124. }};
  125. f1=std::move(f1).resume();
  126. std::cout << "f1: returned first time: " << data << std::endl;
  127. data+=1;
  128. f1=std::move(f1).resume();
  129. std::cout << "f1: returned second time: " << data << std::endl;
  130. data+=1;
  131. f1=f1.resume_with([&data](ctx::fiber&& f2){
  132. std::cout << "f2: entered: " << data << std::endl;
  133. data=-1;
  134. return std::move(f2);
  135. });
  136. std::cout << "f1: returned third time" << std::endl;
  137. output:
  138. f1: entered first time: 0
  139. f1: returned first time: 1
  140. f1: entered second time: 2
  141. f1: returned second time: 3
  142. f2: entered: 4
  143. f1: entered third time: -1
  144. f1: returned third time
  145. The expression `f1.resume_with(...)` executes a lambda on top of fiber `f1`,
  146. e.g. an additional stack frame is allocated on top of the stack.
  147. This lambda assigns `-1` to `data` and returns to the second invocation of
  148. `f1.resume()`.
  149. Another option is to execute a function on top of the fiber that throws
  150. an exception.
  151. namespace ctx=boost::context;
  152. struct my_exception : public std::runtime_error {
  153. ctx::fiber f;
  154. my_exception(ctx::fiber&& f_,std::string const& what) :
  155. std::runtime_error{ what },
  156. f{ std::move(f_) } {
  157. }
  158. };
  159. ctx::fiber f{[](ctx::fiber && f) ->ctx::fiber {
  160. std::cout << "entered" << std::endl;
  161. try {
  162. f=std::move(f).resume();
  163. } catch (my_exception & ex) {
  164. std::cerr << "my_exception: " << ex.what() << std::endl;
  165. return std::move(ex.f);
  166. }
  167. return {};
  168. });
  169. f=std::move(f).resume();
  170. f=std::move(f).resume_with([](ctx::fiber && f) ->ctx::fiber {
  171. throw my_exception(std::move(f),"abc");
  172. return {};
  173. });
  174. output:
  175. entered
  176. my_exception: abc
  177. In this exception `my_exception` is throw from a function invoked on-top of
  178. fiber `f` and catched inside the `for`-loop.
  179. [heading Stack unwinding]
  180. On construction of __fib__ a stack is allocated.
  181. If the __context_fn__ returns the stack will be destructed.
  182. If the __context_fn__ has not yet returned and the destructor of an valid
  183. __fib__ instance (e.g. ['fiber::operator bool()] returns
  184. `true`) is called, the stack will be destructed too.
  185. [important Code executed by __context_fn__ must not prevent the propagation ofs
  186. the __forced_unwind__ exception. Absorbing that exception will cause stack
  187. unwinding to fail. Thus, any code that catches all exceptions must re-throw any
  188. pending __forced_unwind__ exception.]
  189. [#ff_prealloc]
  190. [heading Allocating control structures on top of stack]
  191. Allocating control structures on top of the stack requires to allocated the
  192. __stack_context__ and create the control structure with placement new before
  193. __fib__ is created.
  194. [note The user is responsible for destructing the control structure at the top
  195. of the stack.]
  196. namespace ctx=boost::context;
  197. // stack-allocator used for (de-)allocating stack
  198. fixedsize_stack salloc(4048);
  199. // allocate stack space
  200. stack_context sctx(salloc.allocate());
  201. // reserve space for control structure on top of the stack
  202. void * sp=static_cast<char*>(sctx.sp)-sizeof(my_control_structure);
  203. std::size_t size=sctx.size-sizeof(my_control_structure);
  204. // placement new creates control structure on reserved space
  205. my_control_structure * cs=new(sp)my_control_structure(sp,size,sctx,salloc);
  206. ...
  207. // destructing the control structure
  208. cs->~my_control_structure();
  209. ...
  210. struct my_control_structure {
  211. // captured fiber
  212. ctx::fiber f;
  213. template< typename StackAllocator >
  214. my_control_structure(void * sp,std::size_t size,stack_context sctx,StackAllocator salloc) :
  215. // create captured fiber
  216. f{std::allocator_arg,preallocated(sp,size,sctx),salloc,entry_func} {
  217. }
  218. ...
  219. };
  220. [heading Inverting the control flow]
  221. namespace ctx=boost::context;
  222. /*
  223. * grammar:
  224. * P ---> E '\0'
  225. * E ---> T {('+'|'-') T}
  226. * T ---> S {('*'|'/') S}
  227. * S ---> digit | '(' E ')'
  228. */
  229. class Parser{
  230. char next;
  231. std::istream& is;
  232. std::function<void(char)> cb;
  233. char pull(){
  234. return std::char_traits<char>::to_char_type(is.get());
  235. }
  236. void scan(){
  237. do{
  238. next=pull();
  239. }
  240. while(isspace(next));
  241. }
  242. public:
  243. Parser(std::istream& is_,std::function<void(char)> cb_) :
  244. next(), is(is_), cb(cb_)
  245. {}
  246. void run() {
  247. scan();
  248. E();
  249. }
  250. private:
  251. void E(){
  252. T();
  253. while (next=='+'||next=='-'){
  254. cb(next);
  255. scan();
  256. T();
  257. }
  258. }
  259. void T(){
  260. S();
  261. while (next=='*'||next=='/'){
  262. cb(next);
  263. scan();
  264. S();
  265. }
  266. }
  267. void S(){
  268. if (isdigit(next)){
  269. cb(next);
  270. scan();
  271. }
  272. else if(next=='('){
  273. cb(next);
  274. scan();
  275. E();
  276. if (next==')'){
  277. cb(next);
  278. scan();
  279. }else{
  280. throw std::runtime_error("parsing failed");
  281. }
  282. }
  283. else{
  284. throw std::runtime_error("parsing failed");
  285. }
  286. }
  287. };
  288. std::istringstream is("1+1");
  289. // user-code pulls parsed data from parser
  290. // invert control flow
  291. char c;
  292. bool done=false;
  293. // execute parser in new fiber
  294. ctx::fiber source{[&is,&c,&done](ctx::fiber&& sink){
  295. // create parser with callback function
  296. Parser p(is,
  297. [&sink,&c](char c_){
  298. // resume main fiber
  299. c=c_;
  300. sink=std::move(sink).resume();
  301. });
  302. // start recursive parsing
  303. p.run();
  304. // signal termination
  305. done=true;
  306. // resume main fiber
  307. return std::move(sink);
  308. }};
  309. source=std::move(source).resume();
  310. while(!done){
  311. printf("Parsed: %c\n",c);
  312. source=std::Move(source).resume();
  313. }
  314. output:
  315. Parsed: 1
  316. Parsed: +
  317. Parsed: 1
  318. In this example a recursive descent parser uses a callback to emit a newly
  319. passed symbol. Using __fib__ the control flow can be inverted, e.g. the
  320. user-code pulls parsed symbols from the parser - instead to get pushed from the
  321. parser (via callback).
  322. The data (character) is transferred between the two fibers.
  323. [#implementation]
  324. [section Implementations: fcontext_t, ucontext_t and WinFiber]
  325. [heading fcontext_t]
  326. The implementation uses __fcontext__ per default. fcontext_t is based on
  327. assembler and not available for all platforms. It provides a much better
  328. performance than __ucontext__
  329. (the context switch takes two magnitudes of order less CPU cycles; see section
  330. [link performance ['performance]]) and __winfib__.
  331. [note Because the TIB (thread information block on Windows) is not fully
  332. described in the MSDN, it might be possible that not all required TIB-parts are
  333. swapped. Using WinFiber implementation migh be an alternative.]
  334. [heading ucontext_t]
  335. As an alternative, [@https://en.wikipedia.org/wiki/Setcontext __ucontext__]
  336. can be used by compiling with `BOOST_USE_UCONTEXT` and b2 property
  337. `context-impl=ucontext`.
  338. __ucontext__ might be available on a broader range of POSIX-platforms but has
  339. some [link ucontext ['disadvantages]] (for instance deprecated since
  340. POSIX.1-2003, not C99 conform).
  341. [note __fib__ supports [link segmented ['Segmented stacks]] only with
  342. __ucontext__ as its implementation.]
  343. [heading WinFiber]
  344. With `BOOST_USE_WINFIB` and b2 property `context-impl=winfib` Win32-Fibers are
  345. used as implementation for __fib__.
  346. [note The first call of __fib__ converts the thread into a Windows fiber by
  347. invoking `ConvertThreadToFiber()`. If desired, `ConvertFiberToThread()` has
  348. to be called by the user explicitly in order to release resources allocated
  349. by `ConvertThreadToFiber()` (e.g. after using boost.context). ]
  350. [endsect]
  351. [section Class `fiber`]
  352. #include <boost/context/fiber.hpp>
  353. class fiber {
  354. public:
  355. fiber() noexcept;
  356. template<typename Fn>
  357. fiber(Fn && fn);
  358. template<typename StackAlloc, typename Fn>
  359. fiber(std::allocator_arg_t, StackAlloc && salloc, Fn && fn);
  360. ~fiber();
  361. fiber(fiber && other) noexcept;
  362. fiber & operator=(fiber && other) noexcept;
  363. fiber(fiber const& other) noexcept = delete;
  364. fiber & operator=(fiber const& other) noexcept = delete;
  365. fiber resume() &&;
  366. template<typename Fn>
  367. fiber resume_with(Fn && fn) &&;
  368. explicit operator bool() const noexcept;
  369. bool operator!() const noexcept;
  370. bool operator==(fiber const& other) const noexcept;
  371. bool operator!=(fiber const& other) const noexcept;
  372. bool operator<(fiber const& other) const noexcept;
  373. bool operator>(fiber const& other) const noexcept;
  374. bool operator<=(fiber const& other) const noexcept;
  375. bool operator>=(fiber const& other) const noexcept;
  376. template<typename charT,class traitsT>
  377. friend std::basic_ostream<charT,traitsT> &
  378. operator<<(std::basic_ostream<charT,traitsT> & os,fiber const& other) {
  379. void swap(fiber & other) noexcept;
  380. };
  381. [constructor_heading ff..constructor1]
  382. fiber() noexcept;
  383. [variablelist
  384. [[Effects:] [Creates a invalid fiber.]]
  385. [[Throws:] [Nothing.]]
  386. ]
  387. [constructor_heading ff..constructor2]
  388. template<typename Fn>
  389. fiber(Fn && fn);
  390. template<typename StackAlloc, typename Fn>
  391. fiber(std::allocator_arg_t, StackAlloc && salloc, Fn && fn);
  392. [variablelist
  393. [[Effects:] [Creates a new fiber and prepares the context to execute `fn`.
  394. `fixedsize_stack` is used as default stack allocator
  395. (stack size == fixedsize_stack::traits::default_size()). The constructor with
  396. argument type `preallocated`, is used to create a user defined data
  397. [link ff_prealloc (for instance additional control structures)] on top of the
  398. stack.]]
  399. ]
  400. [destructor_heading ff..destructor destructor]
  401. ~fiber();
  402. [variablelist
  403. [[Effects:] [Destructs the associated stack if `*this` is a valid fiber,
  404. e.g. ['fiber::operator bool()] returns `true`.]]
  405. [[Throws:] [Nothing.]]
  406. ]
  407. [move_constructor_heading ff..move constructor]
  408. fiber(fiber && other) noexcept;
  409. [variablelist
  410. [[Effects:] [Moves underlying capture fiber to `*this`.]]
  411. [[Throws:] [Nothing.]]
  412. ]
  413. [move_assignment_heading ff..move assignment]
  414. fiber & operator=(fiber && other) noexcept;
  415. [variablelist
  416. [[Effects:] [Moves the state of `other` to `*this` using move semantics.]]
  417. [[Throws:] [Nothing.]]
  418. ]
  419. [operator_heading ff..operator_call..operator()]
  420. fiber resume() &&;
  421. template<typename Fn>
  422. fiber resume_with(Fn && fn) &&;
  423. [variablelist
  424. [[Effects:] [Captures current fiber and resumes `*this`.
  425. The function `resume_with`, is used to execute function `fn` in the execution context of
  426. `*this` (e.g. the stack frame of `fn` is allocated on stack of `*this`).]]
  427. [[Returns:] [The fiber representing the fiber that has been
  428. suspended.]]
  429. [[Note:] [Because `*this` gets invalidated, `resume()` and `resume_with()` are rvalue-ref
  430. qualified and bind only to rvalues.]]
  431. [[Note:] [Function `fn` needs to return `fiber`.]]
  432. [[Note:] [The returned fiber indicates if the suspended fiber has
  433. terminated (return from context-function) via `bool operator()`.]]
  434. ]
  435. [operator_heading ff..operator_bool..operator bool]
  436. explicit operator bool() const noexcept;
  437. [variablelist
  438. [[Returns:] [`true` if `*this` points to a captured fiber.]]
  439. [[Throws:] [Nothing.]]
  440. ]
  441. [operator_heading ff..operator_not..operator!]
  442. bool operator!() const noexcept;
  443. [variablelist
  444. [[Returns:] [`true` if `*this` does not point to a captured fiber.]]
  445. [[Throws:] [Nothing.]]
  446. ]
  447. [operator_heading ff..operator_equal..operator==]
  448. bool operator==(fiber const& other) const noexcept;
  449. [variablelist
  450. [[Returns:] [`true` if `*this` and `other` represent the same fiber,
  451. `false` otherwise.]]
  452. [[Throws:] [Nothing.]]
  453. ]
  454. [operator_heading ff..operator_notequal..operator!=]
  455. bool operator!=(fiber const& other) const noexcept;
  456. [variablelist
  457. [[Returns:] [[`! (other == * this)]]]
  458. [[Throws:] [Nothing.]]
  459. ]
  460. [operator_heading ff..operator_less..operator<]
  461. bool operator<(fiber const& other) const noexcept;
  462. [variablelist
  463. [[Returns:] [`true` if `*this != other` is true and the
  464. implementation-defined total order of `fiber` values places `*this`
  465. before `other`, false otherwise.]]
  466. [[Throws:] [Nothing.]]
  467. ]
  468. [operator_heading ff..operator_greater..operator>]
  469. bool operator>(fiber const& other) const noexcept;
  470. [variablelist
  471. [[Returns:] [`other < * this`]]
  472. [[Throws:] [Nothing.]]
  473. ]
  474. [operator_heading ff..operator_lesseq..operator<=]
  475. bool operator<=(fiber const& other) const noexcept;
  476. [variablelist
  477. [[Returns:] [`! (other < * this)`]]
  478. [[Throws:] [Nothing.]]
  479. ]
  480. [operator_heading ff..operator_greatereq..operator>=]
  481. bool operator>=(fiber const& other) const noexcept;
  482. [variablelist
  483. [[Returns:] [`! (* this < other)`]]
  484. [[Throws:] [Nothing.]]
  485. ]
  486. [hding ff_..Non-member function [`operator<<()]]
  487. template<typename charT,class traitsT>
  488. std::basic_ostream<charT,traitsT> &
  489. operator<<(std::basic_ostream<charT,traitsT> & os,fiber const& other);
  490. [variablelist
  491. [[Effects:] [Writes the representation of `other` to stream `os`.]]
  492. [[Returns:] [`os`]]
  493. ]
  494. [endsect]
  495. [endsect]