The best way to approach a new concept is to look at it as a whole and then go back and savor it.

Unless otherwise specified, coroutines refer to programming language-level coroutines and threads refer specifically to operating system kernel threads.

1. What is a coroutine?

Kotlin’s coroutine has been very mature since Experimental V1.1, but there have been various doubts about it. Why? Because even if we get rid of Kotlin, the mere Coroutine itself has been confusing for a long time. Search Coroutine or Coroutine separately if you don’t believe me. Even the father of Lua mentioned why coroutines were rarely seen in early language implementations because the concept was not clearly defined.

And what’s even more interesting is that when you’re looking it up, you often get stuck in this cycle of “Oh, I get it” and “Oh, I don’t know what the hell I know.” And to be honest, I had this same feeling with Lua coroutines when I first learned Lua seven or eight years ago, and then again with Goroutine, Kotlin’s coroutine again, get used to it.

So let’s talk about the concept of coroutines again:

  • Hang back
  • The program handles suspended recovery itself
  • The program itself handles the pending recovery to realize the cooperative operation of coroutines

The key core is that a coroutine is something that can be suspended and then resumed later. Think back to these words whenever you’re in doubt, and even though coroutines may end up looking “fancy” to us, they’re always the same.

Some friends do not understand what is called suspend, suspend the word is actually from the operating system, intuitive understanding, you as a pause to understand it.

2. Why does the concept of coroutines feel confused?

As we mentioned earlier, the concept of coroutines is not really messy, but what is? It is the realization of each family. This is just like Newton’s second law, which seems so simple that F = ma can be used in a variety of ways, and the derived formulas are endless.

Coroutine is to suspend, restore, please tell me how to do suspend recovery. There’s no definition. Since there’s no definition, can we just do whatever we want? Yes, a good cat catches a mouse

Coroutines it with thread is really can’t than ah, the mainstream operating system has mature threading model, the application layer is most often referred to the concept of the thread mapping of differences, so different programming languages once introduced threads, so basically comply with the concept of system threads, thread itself is not their implementation – this is easy to understand, Because thread scheduling is done by the operating system.

Java has great support for threads, which is a key pillar of Java’s success in high-concurrency scenarios, but if you’re interested in taking a look at the underlying thread support in virtual machines, such as Android virtual machines, which are essentially PThreads. Java Object also has a wait method that supports almost any lock implementation, with condition at the bottom.

The vast majority of coroutines are language level implementation, different programming languages have different use scenarios, naturally, the implementation of the coroutine seems to be very different, even some languages do not implement coroutines themselves, but developers through the way of third-party frameworks to provide coroutine capabilities. For example Java framework Quasar (docs. Paralleluniverse. Co/Quasar), plus coroutines implementation itself had a series of evolutions in the operating system level, therefore appeared although theoretically looks very simple, but the implementation is diversified situation.

3. What are the mainstream implementations of coroutines?

We talked about the differences in the implementation of each language, which seems to be very different, mainly in the name of their keywords and types, but in summary, people tend to classify coroutines according to whether they have stacks, that is:

  • Stackful Coroutine: Each Coroutine has its own call stack, somewhat similar to the call stack of a thread. In this case, the implementation of a Coroutine is very similar to that of a thread. The main difference is scheduling.
  • Stackless Coroutine: A Coroutine does not have its own call stack.

Stackoverflowexceptions occur when we recursively call too many levels of functions because stack memory is limited. We have an exception in our program and we always want to see the call relationship between the exception points so that we can locate the problem, and that also requires a stack.

What’s the advantage of having a stack coroutine? Because there is a stack, the runtime can choose to save the stack wherever it is called and suspend the coroutine, which sounds just like threads, except that the program itself, not the operating system, has the right to suspend and resume execution. The disadvantage is also obvious. Every time you create a coroutine, whether it’s running or not, you have to create a stack for it, which is why stackless coroutines are so popular today.

Goroutine doesn’t seem like a coroutine because the developer himself can’t decide on the suspension and recovery of a coroutine; the GO runtime takes care of that itself. To support the ability to suspend goroutine anywhere, goroutine is actually a stacked coroutine, and the GO runtime has done a lot of optimization here. Its stack memory can be expanded and shrunk as needed, usually with a minimum page size of 4KB.

JavaScript, C#, and Python coroutines, or just async/await, are much lighter in comparison and look more like syntactic sugar support for callbacks — they are implementations of stackless coroutines. Stackless, as the name implies, each coroutine does not open a separate call stack, so the question is, how does its context hold?

Which brings us to CPS, the fabled continuation-passing style. So let’s think about it, what’s the key to a program being suspended, or interrupted? Is to save the hang point, or the breakpoint, which is stored in the call stack if the thread is interrupted by the operating system, and where do we store our stackless coroutine? Save it in a Continuation object. This object may be called a Continuation in different languages, but it is essentially a Continuation object. It is just an ordinary object that takes up very little memory. It’s really just an implementation of a Continuation.

The root of Kotlin’s coroutine is a class called Continuation. As I mentioned more than once in a previous post, this guy is a callback if it looks horizontal, resume is onSuccess and resumeWithException is onFailure.

A Continuation carries the context in which the coroutine needs to continue execution, and it is itself a hang point, since all that is needed to resume execution is the body of the function it calls back to. For Kotlin, each suspend function is a suspend point, meaning that the current coroutine may be suspended every time it is called. Each suspend function is inserted by the compiler with an argument of type Continuation to hold the current call point:

    suspend fun hello(a) = suspendCoroutine<Int>{ continuation ->
        println("Hello")
        continuation.resumeWith(Result.success(10086))}Copy the code

We have defined a suspend function called Hello, which appears to accept no arguments. If so, where did we get the continuation we called resumeWith later?

Suspend functions must be called from inside the coroutine, but they are not. When you call suspend functions directly in Java code, you will see that all suspend functions require an additional Continuation.

Java is not necessary, of course, and we can make the suspend function behave as it should with just a little Kotlin reflection:

    val helloRef = ::hello
    val result = helloRef.call(object: Continuation<Int>{
        override val context = EmptyCoroutineContext
        
        override fun resumeWith(result: Result<Int>{
            pritnln("resumeWith: ${result.getOrNull()}")
        })
    })
Copy the code

We can’t call hello() directly, but we can take its function reference and call it with an emission (this may be banned later, but 1.3.50 is still available). If you pass no arguments, The compiler will prompt you that it needs an argument and, look, it capitulated so quickly — the argument it needs is a Continuation.

Again, this code does not need to run in a coroutine body or any other suspend function. Now, why is it official to require a suspend function to run in a coroutine body or any other suspend function?

The answer, of course, is that any coroutine body or suspend function has an implicit instance of a Continuation that the compiler can pass on properly, hiding this detail behind the coroutine to make our asynchronous code look like synchronous code.

At this point, we’re getting close to the essence of a Kotlin coroutine, which is a stackless implementation that is essentially a code + Continuation instance.

4. Is Kotlin coroutine really just a threading framework?

This is actually a very strange statement. If I asked you if a thread is actually a CPU frame, you’d be like, what??

The Kotlin coroutine does provide the ability to cut threads in the process of implementation. That’s its ability, not its identity. It’s like holding a degree that says it’s an ID card.

A degree has a picture and a name, they might say. You can buy a plane ticket with your degree and see if they recognize it.

The world of coroutines can be thread-free if the OPERATING system’s CPU scheduling model is coroutine; The reverse is also true — and I’m sure no one can argue with that. Can a Kotlin coroutine have no threads? At least from the Java virtual machine implementation, it looks like… Not so good. Yes, it’s not, but it’s not the Kotlin coroutine, it’s the Java virtual machine, the Thread of the Java Virtual machine is not so difficult to use, it’s the concurrency support of other languages when it first came out (just like Goroutine did).

We know that Kotlin supports JavaScript as well as Native in addition to Java virtual machines. JavaScript, whether running on the Web or in Node.js, is single-threaded play; Although Kotlin Native can call pthreads, it is officially stated that we have our own concurrency model (Worker) and it is not recommended to use threads directly. Running on both platforms, Kotlin’s coroutine is actually single-threaded, so why is it a thread framework?

At this point, one might wonder, what can a single threaded coroutine do? This front-end students may be more feeling, who told you that asynchronous must be multithreaded. If you want to retrieve a View when your Activity is first created, it will return 0. The layout of the Activity is completed after the onResume method is called, so handler.post is ok:

    override fun onResume(a){
        super.onResume()
        handler.post{
            valwidth = myView.width ... }}Copy the code

This is asynchronous code, but this code is actually running on the main thread, we can use the coroutine rewrite:

    override fun onResume(a) {
        super.onReusme()
        GlobalScop.launch(Dispatchers.Main) {
            val width = hadler.postSuspend {
                myView.width
            }
            Log.d("MyView",widht.toString())
        }
    }
    
    suspend fun <T> Handler.postSuspend(block: () -< T) = suspendCoroutine<T> {
        post {
            it.resume(block())
        }
    }
Copy the code

In fact, I personally think that if the default scheduler of the Kotlin coroutine is Main, and the Main chooses an appropriate event loop according to their respective platforms, it will better reflect the consistency of the Kotlin coroutine across different platforms. For example, for Android, Main is an event loop on the UI thread. For Swing, which is also a UI event loop, any platform that has an event loop will have a scheduler based on this loop by default. Kotlin coroutines already have a runBlocking, so for ordinary Java programs that don’t have an event loop, just construct one.

The designers of Kotlin coroutines didn’t do this, and they certainly had a point. They didn’t want to force developers to use coroutines, or even change their code right away. They wanted Kotlin to be a programming language with enough security and a flexible syntax. The rest is up to the developers to choose.

5. Do coroutines really have advantages over threads?

That’s not an easy question to answer.

When Kotlin coroutines first came out, some people did performance comparisons and thought that coroutines didn’t have any performance advantages. It’s fair to say that his testing methods are professional, and there are certainly scenarios in which there is no performance advantage to using coroutines, just as we need to run a computationally intensive program with multiple threads on a single CPU. Every feature has its scenarios and its areas.

We see all kinds of explanation coroutines article will mention coroutines lighter than the thread, this in fact we also explained, coroutines programming language level is within the program logic, does not involve switching between the resources of the operating system, the operating system’s kernel thread will naturally heavier, not to mention every create a thread will open up the stack memory overhead, Thread context switching requires the CPU to clear the cache and replace the memory of the next thread from memory, and handling the breakpoint saving of the previous memory is an expensive task. If it’s not intuitive, imagine the company leader sending a message to your wechat group asking why your activity went down just as you were about to kill five.

Threads, in addition to the kernel thread’s ability to execute code itself, are often given the concept of logical tasks, so coroutines are a lightweight “thread” term that describes more of its usage scenarios, which might be more appropriate:

Coroutines are more like lightweight “threads”.

Threads naturally enjoy the advantage of parallel computation, whereas coroutines can only rely on the threads within the program to implement parallel computation. In fact, the advantage of coroutines is more in IO intensive programs, which may be a very confusing thing for Java developers, because for so many years, few people use NIO, most of them use BIO to read and write IO, so whether open threads or open coroutines, There is always a thread waiting for the IO when reading or writing IO, so it doesn’t seem to make any difference. However, NIO is not the same, IO does not block, by opening one or a few threads to select IO events, there are I/O events arrived in the corresponding thread to read and write I/O, compared to the traditional I/O has a great improvement.

Hey? Are there any mistakes? Are you writing about threads?

Yes, NIO itself reduces thread usage, yes. But what about coroutines? Coroutines can be based on the ideas to further simplify the code group, while the thread can solve a problem, but is actually very tired, to write coroutines can let you more easily, especially when multiple tasks need access to public resources, if a thread to handle each task distribution, then there will have a thread will spend a lot of time waiting for locks, But if we use coroutines to host the task, with very few threads to host the coroutine, then lock optimization becomes simple: if the coroutine fails to acquire the lock, the coroutine hangs, and the corresponding thread can go out and run another coroutine.

I prefer to think of coroutines as an abstraction that is closer to the level of business logic or even human thought, which is actually a higher level of abstraction than threads. Threads can make our programs run concurrently, and coroutines can make concurrent programs run better.

The thread itself is fine. Why use a coroutine? It’s like we’re often asked why I should use Kotlin when Java is the answer. Why do you say that?

6. Summary

In general, whether it is asynchronous code synchronization or concurrent code simplification, the emergence of coroutines actually provides the possibility for code from computer to human thinking close.


Welcome to Kotlin Chinese community!

Chinese official website: www.kotlincn.net/

Official Chinese blog: www.kotliner.cn/

Public id: Kotlin

Zhihu column: Kotlin

CSDN: Kotlin Chinese Community

Nuggets: Kotlin Chinese Community

Brief: Kotlin Chinese Community