Coroutine General translation is the process, similar to the thread can switch, and the thread is by the operating system scheduler to implement the switch is not the same, the process by the user program to switch their own scheduling. I have seen the related content of the process before, but I did not realize it myself. Recently Openstack,openstack each module is a single-threaded model, but with the Eventlet green thread, Eventlet is also a Python implementation library. This article I do not intend to analyze the implementation of the Python libraries, but the analysis of a Linux-based Ucontext component of the C language implementation, the original author is the Cloud wind, I have seen this implementation, but now forget, did not write or analyze the code, just look like always indefinitely. Later Yanyiwu and fork a realization and make some changes, it is said to be more understandable, I will directly take his revised version of the analysis is OK, here to thank them.
This simple implementation contains three files, namely the header file Coroutine.h, the implementation of the file coroutine.c and the test main program MAIN.C, I added a note to the code, and compiled run.
Coroutine.h Source:
Coroutine.h inside are some macro definitions and function declarations:
Coroutine_func: A function pointer that declares a function prototype of coroutine;
Coroutine_open: The first function to be called when the coprocessor is to be used, it returns a scheduler structure body;
Coroutine_close: Close the co-scheduler, the last call does not explain;
Coroutine_new: Add a function and the parameters that need to be passed into the scheduler of the co-process;
Coroutine_yield: Exits the current running process;
Coroutine_resume: Restores the association with a specific ID value;
Coroutine_running: Returns the running Coprocessor id,-1 indicating that there are no running threads;
Schedule_status: Return 1 indicates that there is still a waiting run, and returning 0 means that all the threads have been run;
The main implementation is in the coroutine.c file, the source code is as follows:
COROUTINE.C source code We do not analyze, a moment to analyze main.c will naturally talk about it.
MAIN.C source code is as follows:
Let's analyze the code for the MAIN.C. First look at the main function, call the Coroutine_open function, return a scheduler structure, and then call the test function and the scheduler structure as a parameter, and finally call the Coroutine_close function to close the scheduler. Obviously, the test function is where the dirty live dirty are connected. Look at the test function in 35, 36 lines, called Coroutine_new to create two threads, respectively, using the function foo and foo2, the parameters are Arg1 and arg2, and return the association ID, respectively, co1 and CO2. Then there is a while loop that looks at the code:
while (Schedule_status (S)) {
Coroutine_resume (S,CO1);
Coroutine_resume (S,CO2);
}
As you can see, when Schedule_status returns to 1 o'clock, the Coroutine_resume function is called on the Co1 and CO2 respectively , and Schedule_status returns 0 o'clock the test function exits. This time, we have to look at the Coroutine_resume function:
The Coroutine_resume function has two parameters, namely the scheduler structure and the Association ID. The function first obtains the corresponding coprocessor structure from the scheduler according to the Association ID, and then the state status is judged, the possible states are Coroutine_ready and coroutine_suspend.
When the status is coroutine_ready(the first time the coprocessor is dispatched):
call GetContext to get the current (note, not currently pass in the ID of the corresponding association) the context of the association, save in the passed in the ID corresponding to the structure of the Association of the type of ucontext_t, and then modify the CTX structure of the stack pointer and stack size, and set the process context to be executed when the process exits to the scheduler structure body type ucontext_t variable main, and then set the scheduler structure in the body of the running variable to the ID of the association to be executed, the state status of the process to be performed is set to Coroutine _running, then call Makecontext Modify to execute the context of the process, the parameter is to hold the thread of the context variable, MainFunc function address, the number of mainfunc parameters, the parameters passed to MainFunc, so the subsequent execution of the coprocessor, The MainFunc function is called and finally called Swapcontext, which saves the context contents of the current thread in the main variable of the scheduler struct and activates the context of the thread to be executed, so the MainFunc function is called.
status is when coroutine_suspend :
The variable RUNNING in the scheduler structure is set to the parameter ID passed in, and the associated state status of the ID is set to coroutine_running, which calls Swapcontext to save the current context Activates the associated process for the execution parameter ID. When the process is in this state, it must have been passed through the coroutine_ready stage, its stack pointer has been modified, so do not need to modify and directly activate the execution.
It is not difficult to see that the first time each process is dispatched, the Makecontext function is called and the MainFunc function is set to the function to be called when the coprocessor executes, so we know that the corresponding functions Foo and foo2 of the co1 and CO2 are called in MainFunc. Let's look at the implementation of Foo and Foo2:
Both functions have a for loop, and each loop calls the Coroutine_yield function, which first changes the state status of the current process to coroutine_suspend and sets the running variable in the scheduler structure to-1, Call Swapcontext to save the process context in the struct variable ctx of the current coprocessor, activating the context of the main variable in the scheduler structure, where it is actually switched to the main coprocessor.
Speaking of this, some students are still unclear, I based on their understanding of the specific to explain the process:
While loop inside the CO1 call Coroutine_resume, because the first call into the Coroutine_ready branch, this time GetContext get the main process (do not know the description is right) context, Then after modifying the stack as the coprocessor context is saved in the co1 for the coprocessor struct, and then MainFunc executes co1 for the function foo, called Coroutine_yield in Foo, when CO1 is set to Coroutine_suspend, Switch to the main process just saved, this is the test function inside the Coroutine_resume is called, but this is CO2, the same fate, CO2 corresponding Foo2 is scheduled to execute, did not think Foo2 function automatically set itself to Coroutine_suspend, then switched to the main process, the test again in the beginning of the loop, Coroutine_resume to Co1 call, just this time into the Coroutine_suspend branch, which does not have to set what stack, Switch directly to the implementation of the CO1, the same for CO2, no longer repeat.
So the question comes again, when does co1 and CO2 end and the main program exits?
The number of for loops in Foo and Foo2 is limited, and when the loop condition is not met, the Coroutine_yield function is not called, at this point the call in MainFunc: "C->func (S, C->arg); "End, after the statement" c->status = Coroutine_dead; " is called to set the corresponding association state status to Coroutine_dead. When both of the process states are coroutine_dead, the Schedule_status function returns 0, and the main program exits when the while loop exits. Looking at the execution of the program, everything becomes clear.
Operation Result:
Coroutine code Analysis using the Ucontext component implementation