As a programmer, you’ve probably heard of the term coroutine, and it’s a technology that’s been popping up more and more in recent years, especially in high-performance, high-concurrency areas. If your mind goes blank when a classmate or colleague mentions coroutines…

Then this article is for you.

Without further ado, today’s topic is how to understand coroutines once and for all as a programmer.

Ordinary function

Let’s start with a common function, which is very simple:

def func():
   print("a")
   print("b")
   print("c")
Copy the code

This is a simple ordinary function. What happens when we call this function?

  1. Call the func
  2. Func starts execution until return
  3. Func completes, and returns function A

Isn’t it simple, the function func executes until it returns, and prints:

a
b
c
Copy the code

So easy, no, no!

Very good!

Note that this code is written in Python, but this discussion of coroutines applies to any language, and we just happen to use Python as an example because it’s simple enough.

So what is a coroutine?

From ordinary functions to coroutines

Now, we’re going to go from ordinary functions to coroutines.

Coroutines can have multiple return points, as opposed to just one return point for a normal function.

What does that mean?

Void func() {print("a") print("b") print("c")}Copy the code

In normal functions, the function returns only when print(“c”) is finished, but in coroutines, when print(“a”) is finished, the func returns to the calling function with the “pause and return” code.

Some of you might be confused. Is there any magic in that? I can also write a return, like this:

Void func() {print("a") return print("b") pause and return print("c")}Copy the code

It is also possible to write a return statement directly, but if you do so, the code after the return will not be executed.

The magic of coroutines is that we can continue to call the coroutine when we return from it, and continue to execute from the last return point of the coroutine.

This is enough magic, just like the Monkey King said “steady”, the function is suspended:

Void func () {print (" a ") on the print (" b ") on the print (" c ")}Copy the code

At this point we can return to the calling function, which can call the coroutine again whenever it remembers it, and the coroutine will continue execution from the last return point.

Amazing, yes, yes, yes!

Very good!

However, the formula used by Monkey King is generally called yield in programming languages (other languages may have different implementations, but the essence is the same).

It is important to note that when ordinary function returns, the process’s address space won’t save any information on this function at runtime, and coroutines returns, the runtime information () function needs to be preserved, the function of runtime state exactly in memory is what appearance, you can refer to here for this problem.

Next, let’s look at coroutines in real code.

show me the code

Let’s use a real example to illustrate this. The language is Python, so don’t worry if you’re not familiar with it. There’s no barrier to understanding.

In Python, the “definite” word also uses the keyword yield, so our func function becomes:

void func() {
  print("a")
  yield
  print("b")
  yield
  print("c")
}
Copy the code

Note that our func is no longer a simple function, but a coroutine. How can we use it? It’s very simple:

1 def A(): 2 co = func() # print("in function A") # do something 5 next(co) # do somethingCopy the code

We can see that even though the func function has no return statement, which means that it does not return any value, we can still write co = func(), which means that co is the coroutine we got.

Next we call the coroutine, use next(co), and run function A to see what happens when we get to line 3:

a
Copy the code

Obviously, as expected, the coroutine func pauses for yield after print(“a”) and returns function a.

Next comes line 4, which, no doubt, A is doing something of its own, so it prints:

a
in functino A
Copy the code

Coming up on the important line, what should be printed when the coroutine is called again after line 5?

If func were a normal function, the first line of func would be executed, printing a.

But func is not an ordinary function, it is a coroutine, and as we said before, the coroutine will continue to run at the last return point, so what should be executed here is the code after the first yield of the func function, which is print(“b”).

a
in functino A
b
Copy the code

You see, a coroutine is an amazing function that remembers the state of its previous execution and when called again picks up where it left off.

Amazing or not! Amazing or not!

Very Good.

Graphical interpretation

To give you a more thorough understanding of coroutines, let’s look at them graphically again, starting with ordinary function calls:

In this figure, the boxes represent the sequence of instructions for the function. If the function does not call any other functions, it should be executed from top to bottom. However, other functions can be called within a function, so execution is not simply top-down.

As we can see from the figure, we first go to funcA, and after executing it for a while, we find that another function funcB is called. At this time, control is transferred to this function, and after executing it, we return to the call point of main to continue executing.

This is a normal function call.

Then we have coroutines.

Again, we execute first in funcA, run for a while and then call the coroutine. The coroutine executes until the first hang point, and then returns funcA as a normal function. FuncA executes some code and calls the coroutine again. Instead of executing from the first instruction, the coroutine is executed from the last hang point, and after some time it encounters a second hang point, at which point the coroutine returns funcA like a normal function, funcA being executed for some time and the program ends.

A function is just a special case of a coroutine

So, what’s the magic? Unlike normal functions, coroutines know where they were last executed.

As you can see by now, the coroutine saves the running state of a function when it is paused, and can recover from the saved state and continue running.

A thread can also be suspended. The operating system saves the running status of a thread and then dispatches another thread, which can then continue running as if it had not been suspended when it is allocated CPU again.

It’s just that thread scheduling is implemented by the operating system, which is not visible to the programmer, whereas coroutines are implemented in user mode, which is visible to the programmer.

That’s why some people say you can think of coroutines as user-mode threads.

There should be applause.

This means that now that the programmer can play the role of the operating system, you can control when the coroutine runs and when it pauses, which means that you can schedule the coroutine yourself.

When it comes to coroutines, scheduling is up to you.

When you write yield in a coroutine you want to pause the coroutine, and when you use next() you want to run the coroutine again.

Now you understand why a function is just a special case of a coroutine, a function is really just a coroutine without a hanging start.

The history of coroutines

Some of you may think that coroutines are a relatively new technology, but the concept of coroutines was first introduced in 1958, before the concept of threads was even introduced.

By 1972, the concept was finally implemented by Simula 67 and Scheme.

But the concept of coroutines never caught on, and there was even an archaeological paper in 1993 digging up the ancient technology.

Because there were no threads at this time, if you wanted to write concurrent programs in the operating system you would have to use something like coroutines, and then threads came along, and the operating system finally started to support concurrent execution of programs natively, and then coroutines went out of the programmers’ way.

Until recent years, with the development of the Internet, especially the advent of the mobile Internet era, the server side has higher and higher requirements for high concurrency, and coroutines have once again returned to the mainstream of technology, and all major programming languages have supported or planned to support coroutines.

You should now have a clear idea of coroutines.

conclusion

By now you should have understood what coroutines are all about, but the question remains, why are coroutines back in the spotlight, and in what context are they suitable? How do you use it?

The next article will give you answers to these questions.

Hopefully this article helped you understand coroutines.