preface

The coroutines discussed in this paper are mainly kotlin, and may refer to Python and Go. However, we will try to avoid using codes, and try to talk about the development of coroutines in popular language to ensure that everyone can understand them.

In recent years, some programming language upstart Go and Kotlin have introduced the coroutine language feature, so that the concept of coroutine, which seems to be very strange, began to come into view frequently, in order to facilitate understanding, developers are treating it as the younger brother of threads, namely lightweight threads. But to be more specific, coroutines are actually a programming concept that appeared very early, even before threads, but in terms of the status of programming languages, coroutines are inferior to threads, so it is not strange to bow to threads.

After reading my introduction above, everyone must be very puzzled. You said that coroutines appeared earlier and had experience. Then why did the development of programming languages for decades become such a mix of salty fish? Threads came late, but a single spark lit the prairie of programming languages. Has become an important concept in programming languages? Moreover, coroutine has been a salt fish for decades, but now how can it suddenly have a dream and turn over to sing?

Today, I will sort out the whole development process of coroutines with you, hoping to help you better understand coroutines.

The emergence of coroutines

Let’s start with the history of coroutines and how they got so screwed up, because a miserable life needs an explanation.

Coroutines were first developed in 1958 and used in assembly language (more than 60 years ago). The full definition of coroutines was published in 1963. Coroutines are program components that implement collaborative multitasking by resuming and suspending code execution.

Threads, meanwhile, came a little later, around 1967, with the advent of the operating system. Threads, as the minimum execution component scheduled by the operating system, are mainly used to achieve preemptive multitasking.

Since everyone multitasks, no one is much better than the other, and coroutines are still a few years old, it is theoretically possible to work their way up to the core of a programming language.

But the development of this coroutine, on the one hand, of course, depends on self-struggle, on the other hand, also need to consider the historical process. In the 1970s, ’80s and’ 90s, computers were moving crazily toward miniaturization and personalization. Computers relied heavily on operating systems to maximize user interaction and squeeze CPU performance. How did operating systems squeeze computer performance? By multithreading. After the operating system followed the popularity of the personal computer, programming languages naturally began to rely on the interface provided by the operating system to control the computer, and threading became an important concept that almost all programming languages could not skip, and has continued to this day.

At this point, you might ask, since we’re all multitasking, why do threads improve CPU utilization and coroutines don’t?

There are many other reasons for this, but the bottom line is that coroutines and threads are very different concepts, and we’re going to come back to what is cooperative multitasking and what is preemptive multitasking? And are these two tasks the same concept?

  • Collaborative multitasking:

The above picture shows part of a sushi production process. We can think of the conveyor and the gripper in the picture as two tasks, working together to complete the food production. This is collaborative multitasking. Collaborative multitasking requires that tasks be familiar with each other to achieve collaboration.

  • Preemptive multitasking:

In the goldfish feed scenario, all the goldfish immediately rush to grab the food. Each goldfish is equivalent to a task thread. This is called preemptive multitasking. While preemptive multitasking (threads) does not require understanding and cooperation, only competition.

The two graphs above vividly illustrate the difference between cooperative multitasking (coroutine) and preemptive multitasking (multithreading). We can see that coroutines are better suited to tasks that are familiar to each other. The “task” in cooperative multitasking is a subprogram (called a function). A task in preemptive multitasking is a component or code that can seize resources (i.e., threads), in this case multithreading. So coroutines and threads were very different concepts, and their capabilities were very different, and the capabilities of threads were very much in line with the needs of the time. Self – work + history is the main reason for thread success.

Of course, on the other hand, because coroutine is a concept based on programming language level, there is no unified definition of interface, so the effect of implementation in different languages is different, which will cause great trouble to developers and is not conducive to its promotion. On the other hand, threads, through the unified interface of the operating system, define roughly the same way of using threads, ensuring that different programming languages are roughly the same use of threads.

At this point, let’s summarize the reasons for the early development of coroutines

  • 1. Coroutines do not represent the development requirements of advanced productivity, the direction of advanced culture, and the fundamental interests of the broadest developers.
  • 2. The actual performance of coroutines varies in different programming languages, which is not conducive to the understanding and use of developers.

These two points explain why coroutines have been tepid for decades. We also see that while it may seem like multitasking, coroutines and threads don’t actually overlap much.

turnaround

Although coroutines are collaborative multi-tasking components that don’t make programs more efficient, they don’t seem to have a wide range of applications, but you can’t deny yourself that at some point you’ll suddenly be taken care of by the historical process.

Let’s start with threads. While threads have become an important concept in the programming world, developers have come to realize their pain points over the years:

  • Threads (asynchronous code) are difficult to interact with, and often only callback is used. A large number of callbacks make the code difficult to read and understand, and ultimately make the project difficult to maintain.

To put it simply, on the developer side, threads can interact with each other more conveniently.

And what does the coroutine do here?

Maybe I’ll restate the thread pain point: on the developer side, how threads can collaborate more easily.

Remember, how did we introduce coroutines? Collaborative multitasking, right? Remember the turntable and the gripper in the picture above? We said the two tasks were more like two functions working together, but what if the turntable and the gripper were two threads? With a compiler that wraps threads into functions that can be paused and resumed, can threads collaborate as coroutines are designed to do?

Let’s look at how coroutines are used today at the code level. Design a simple requirement: when Posting in the community, users need to verify the Posting permission from the background first and request two interfaces, so maybe we need to try to open two lines to finish later.

  • Normal callback code:

fun tryPost(){// Verify permissions through the interface (actually start a thread, Then wait for the callback) findUserPermission(user,callback()){public void onSuccess(UserPermission Response){// If the callback succeeds, check whether the permission is availableif(response.haspermission){// access the post interface if it hasPermission. PostContent (content,callback()){public void onSuccess(Result response){// Handle successful Response} public voidonFail() {}}}else{// If no permission,...... } } public voidonFail() {}}}Copy the code

This is a common practice, we need to access the interface twice, and the interaction can only be done in the callback, but the code is already ugly, if there is any other logic, the code would be much more verbose and difficult to maintain.

So how does a coroutine deal with this pain point? Let’s look at how the (Kotlin and Python) coroutine code implements this requirement:

  • Kotlin’s coroutine code
// The function passessuspendThe keyword identifier, which can be called by coroutines, has the ability to pause and recover, and actually still uses the IO thread to complete the interface requestsuspendFun tryfindUserPermission () : PermissionResponse {returnWithContext (dispatchers.io){findUserPermission(user)}} // The function passessuspendKeyword identification that can be called by coroutinessuspendFun post () : the Result {returnWithContext (dispatchers.io) {postContent(content)}} funtryPostVar response = tryfindUserPermission() {// Start a coroutine launch{// The code executes on this line, gives up the CPU, and resumes execution after the request succeedsif(response.hasPermission){var response = post() // Handle Responseif need
        }
     }
 }
Copy the code

As you can see, in Kotlin, coroutines make the interaction between multiple threads as simple as normal functions, without the need for callback, by encapsulating the code in the thread into a pause/resume function.

  • Python coroutine code
Import asyncio // async keyword, stating that this function can be called by coroutons async def findUserPermission(): // Handle HTTP Request... .returnresponse async def postContent(): // handle http request ... .returnResponse async def main(): response async def main(): response async def main(): Response async def main(): Reponse = await findUserPermission()if response.hasPermission :
        res = await postContent()
        // handle response if need

asyncio.run(main())

Copy the code

Python’s approach to this problem through coroutines is essentially kotlin’s.

As you can see, coroutines behave differently in different languages

As we can see from the above pseudocode, coroutines can significantly simplify collaboration between threads, allowing us to write asynchronous code in the same way we write synchronous code, greatly simplifying your logic and making your code easier to maintain.

So how does coroutine work?

Although coroutines vary from language to language, the principle is the same. Compilers in programming languages modify functions with keywords (suspend in Kotlin, async in Python, etc.), and generate thread related code during compilation according to the keywords to achieve the function of suspend and resume. This allows thread-specific code to be generated at compile time, providing collaboration at the development level as if it were a normal function.

By addressing this pain point, coroutines are becoming more and more popular with developers. With the help of the compiler, coroutines leave thread-related code to be generated during compilation, which can be developed by manipulating coroutines to achieve the purpose of using threads. Therefore, coroutines are now considered to be lightweight threads.

For multithreaded collaboration, or asynchronous code cooperation not only between coroutines a solution, in JS, promise, in Java have RxJava and so on, they are committed to solve the related problem of asynchronous programming, can want to write in the form of synchronization code asynchronous code, for now, they are all doing very well.

conclusion

There’s a lot of disagreement about what coroutines are, but to me, coroutines are really two stages of understanding

  • At the beginning, coroutines were just programming components used to solve special problems in programming. Their multitasking was more like a combination of functions. At that time, coroutines were actually more like a function with pause and recovery. But this functionality didn’t seem to be popular, so coroutines remained relatively niche for a long time. (At this point coroutines do not have much to do with threads)
  • It is now a collaborative multi-tasking component that supports multithreading at the bottom, which is a good solution to the pain point of thread collaboration, and is gradually becoming more and more popular. Coroutines and threads become more intimate, and they seem to become more similar. (Today you can think of coroutines as lightweight threads.)

And the development course of coroutine is actually experienced these two stages.