C ++ asynchronous programming library based on Fiber (I): Principle and example, fiber asynchronous
Fiber and coroutine are similar concepts, such as user-level threads or light threads. Windows provides a set of APIS for users to create and use fiber courses. The libraries in this article are implemented based on this set of APIs. Therefore, they cannot be used across platforms. Non-Windows programmers can be confused, of course, if you are interested, you can continue to look at it and find a third-party coroutine library to encapsulate it to achieve the same effect. For more detailed information about the fiber process, refer to MSDN.
There are two key points in the concept of Fiber Process:
It demonstrates the process of switching several fiber threads. Note that each fiber path has an independent stack and switches to another fiber path through the SwitchToFiber function:
For comparison, we can take a look at the stack changes in the function call process. The following shows the common nested call relationships of func1-> func2-> func3:
Each worker function call creates a new stack frame, which together forms the entire call stack. When the function returns, its stack frame is released. For function calling, we can be sure that (without throwing an exception) after the called function is executed, it will return at the call point and continue to execute the next statement. However, the call (switch) between fiber ranges is different. One fiber range can be switched to another fiber range at any position, and it may never be switched back, it may also be switched back from any other fiber (not just switched). The preceding description is only a very simple situation, and the actual situation may be very complicated, it's hard to figure out the arrow that jumps and jumps from the complexity to the export. Switching between fibers is a bit like using an enhanced version of goto. It is nice to use it, but the subsequent maintenance is troublesome.
So just like replacing goto With while/for/switch-case, we also need to encapsulate a new set of APIs to replace direct calls to the operating system APIs. On the one hand, in the encapsulation process, we can impose some security constraints on the behavior of the Fiber Process (actually the behavior of programmers, this makes it easier to write secure code or write Insecure code. On the other hand,Process control statements from goto to while/switch are actually an improvement in the Abstraction LayerFor most common requirements, the latter is more convenient to use and less prone to errors, and the code written is more concise and easy to understand. Similar, from the system API to the new encapsulated API or encapsulation class, it is also an improvement in the abstract level, which can be more convenient for applications in various business scenarios. Finally, directly Using System APIs requires writing a lot of auxiliary code to maintain the fiber process. This type of code is usually repeated and distributed to every corner of the Business Code, further reducing the readability of the program and improving the maintenance difficulty, encapsulation is also used to solve this problem.
Now, let's try the last piece of code:
1 const int RUN_TIMES = 5; 2 3 int number = 0; 4 bool shutdown = false; 5 6 Fiber fib ([& number, & shutdown] 7 {8 while (! Shutdown) 9 {10 number ++; 11 Fiber: yield (); // A: Control transferred to primary Fiber 12} 13 }); 14 15 for (int I = 0; I <RUN_TIMES; I ++) 16 {17 fib. resume (); // B: Switch to sub-fiber run 18} 19 20 printf ("number = % d \ r \ n", number );
Here, we first create a fiber program to accumulate the number variable, and then execute it in the for loop (just use this word) to get the correct result. The code in the two parts of AB respectively implements fiber-way switching. In fact, it encapsulates the call to SwitchToFiber. Note that the call details of the two functions are different: resume indicates the fiber-way to switch to the object packaging, which isCommon member functions, Yield indicates that the control is handed over to the caller, yesStatic member functionsYou can think about the differences between static and non-static member functions.
The following is the code for implementing the producer-consumer model using a fiber process:
1 int product_count = 0; 2 bool is_end_time = false; 3 4 const int RUN_TIMES = 3; 5 6 // producer length 7 Fiber _ producer ([& is_end_time, & product_count] 8 {9 srand (unsigned) time (NULL); 10 11 while (! Is_end_time) 12 {13 int new_product_count = (int) (double) rand ()/RAND_MAX * 10) + 1; 14 product_count + = new_product_count; 15 16 printf ("[producer] create new products: % d \ r \ n", new_product_count); 17 18 Fiber: yield (); 19} 20 21 printf ("[producer] off duty. \ r \ n "); 22}); 23 24 // consumer fiber execution function 25 auto consumer_proc = [& is_end_time, & product_count] (const int seq_number) 26 {27 int total_count = 0; 28 29 whil E (! Is_end_time) 30 {31 if (product_count> 0) 32 {33 product_count --; 34 total_count ++; 35 printf ("[consumer % d] got 1 product, total got % d, remain % d \ r \ n ", seq_number, total_count, product_count); 36} 37 38 Fiber: yield (); 39} 40 41 printf ("[consumer % d] off duty. \ r \ n ", seq_number); 42}; 43 44 const int CONSUMER_COUNT = 3; 45 int consumer_seq_number = 0; 46 47 // create a consumer fiber array 48 std :: vector <Fiber> consumer_array (CONSUMER_COUNT); 49 std: for_each (consumer_array.begin (), consumer_array.end (), [&] (Fiber & item) {item = Fiber ([&] {consumer_proc (consumer_seq_number) ;}); consumer_seq_number ++ ;}); 50 51 consumer_seq_number = 0; 52 53 for (int I = 0; I <RUN_TIMES; I ++) 54 {55 maid (); 56 57 while (product_count> 0) 58 {59 consumer_array [consumer_seq_number]. resume (); 60 consumer_seq_number = (consumer_seq_number + 1) % CONSUMER_COUNT; 61} 62} 63 64 is_end_time = true; 65 66 // wait until the Fiber end 67 Fiber :: await_all (consumer_array); 68 Fiber: await (fib_producer );
The await and await_all methods at the end of the program can be left empty without affecting the main logic. BecauseAll Fiber threads run in the same thread, so no locks are required.This is also an important benefit of using fiber, and is also one of the main purposes of our encapsulation library.
Due to space limitations, I only wrote so much this time. More content will be included in the post, and a total of four or five articles will be written. But the Code has actually been written. You can view the code at this address:
Https://code.csdn.net/xrunning/fiber
I created a QQ group: micro-architecture design 165241092, mainly discussing C ++ code-level design. Interested friends will join in to discuss and learn.