preface

As Kotlin becomes more and more popular in Android development, coroutines are becoming more and more widely used in various projects. But what exactly is a coroutine? Coroutine is actually an old concept, has been very mature, but there has been a lot of doubt about its concept, some people say that coroutine is a lightweight thread, some people say that kotlin coroutine is actually a set of thread switching scheme

Obviously this is not very friendly to beginners, when you don’t know somethingWhat is theIt’s hard to get inwhyandWhat do I doThe stage

This article mainly answers this question, mainly includes the following content

1. Some pre-knowledge about coroutines

2. What is a coroutine?

3.kotlinSome basic concepts of coroutines, hang functions,CPSTransitions, state machines, etc

The above questions are summarized as mind maps as follows:

1. Some pre-knowledge about coroutines

To understand coroutines, we can start from the following points: 1. What is a process? Why have a process? 2. What are threads? Why have threads? What’s the difference between a process and a thread? 3. What is collaborative and what is preemptive? 4. Why introduce coroutines? To solve what problem?

1.1 What is a Process?

When reciting the definition of a process, we may often see a sentence

A process is the smallest unit of resource allocation

How do we understand this allocation of resources?

On a single-core CPU, only one program is running in memory at a time

If you have two programs, program A and program B, and program A is running, and A needs to read A lot of input data (IO operations), the CPU can only wait until the data of program A is read, and then continue to execute program B after the execution of program A.

This is a waste of CPU resources, and we would probably prefer the following

When program A reads, switch to program B, and when program A finishes reading, pause program B and switch back to program A, okay?

Switching in a computer The term is subdivided into two states:

Suspend: Save the current state of the program, pause the current program; Activation: restore the program state and continue to execute the program;

This kind of switch, involves saving and restoring the program state, and procedures needed for the A and B system resources (memory, hard disk, etc.) are not the same, it also need A thing to record the program A and B respectively what kind of resources, and system control program to switch A and B, to be A marker to identify, etc., so there is A process of abstraction.

1.1.1 Process Definition

Process is a process of dynamic execution of a program with certain independent functions on a data set. It is an independent unit for the operating system to allocate and schedule resources. It is the carrier of application program running, mainly composed of the following three parts. Program: Describes what a process does and how it does it; 2. Data set: resources required during the execution of the program; 3. Process control block: records the external characteristics of the process, describes the process of executing changes, and the system uses it to control and manage the process. The system is the only symbol of the existence of the process.

1.1.2 Why a process

In fact, we have analyzed above, the operating system to support multiple processes, is in order to improve the utilization of the CPU To switching process, need to process and recovery support, and resources of different between different processes, so that is why process need isolation between resources, which is the cause of the process are the smallest units of resource allocation

1.2 What is a thread?

1.2.1 Definition of threads

A lightweight process, the basic unit of CPU execution, and the smallest unit of program execution. It consists of thread IDS, program counters, register combinations, and stacks. The introduction of threads reduces the overhead of concurrent execution and improves the concurrent performance of the operating system.

1.2.2 Why threads?

The problem is well understood. Processes allow multiple programs to execute concurrently, improving system efficiency and resource utilization, but with the following problems:

  1. A single process can only do one thing, and the code in the process is still executed sequentially.
  2. If execution is blocked, the entire process hangs, and even if some work in the process does not depend on the waiting resource, it will not execute.
  3. Memory cannot be shared between multiple processes, which makes inter-process communication difficult.

Threads were created to reduce context switch consumption, increase concurrency in the system, and break through the limitation that a process can only do one thing and make in-process concurrency possible.

1.2.3 Differences between processes and Threads

  • 1. A program has at least one process, and a process has at least one thread, which can be understood as the container of threads;
  • 2. The process has an independent memory unit during execution, and multiple threads in the process share the memory.
  • 3. The process can be extended to multiple machines, and the thread is suitable for multiple cores at most;
  • 4. Each independent thread has a program running entry, sequential execution column and program exit, but it cannot run independently. It depends on the application program, and the application program provides multiple threads for execution control;
  • 5. Process is the minimum unit of resource allocation, and Thread is the minimum unit of CPU scheduling
  • 6. Both processes and threads are a description of a time period, yesCPUThe description of the working time, but the particle size is different.

1.3 Collaborative and preemptive

On a single-core CPU, with only one process running at any one time, how do you allocate the CPU’s time slice?

1.3.1 Collaborative multitasking

Early operating system is the use of cooperative multitasking, that is, by the process to actively give out the execution right, such as the current process needs to wait for IO operation, actively give out the CPU, by the system scheduling the next process. Each process routine work and let out the yield the CPU, CPU is very harmonious, but there is also a hazard: a single process can be fully occupied in the computer CPU processes the good and bad are intermingled, regardless of the sinister process, if the process is with poor robustness, run midway in the infinite loop, a deadlock, etc., can cause the whole system paralysis! In this mixed environment, it is surely unscientific to entrust execution to processes themselves, and so preemptive multitasking, controlled by the operating system, is born

1.3.2 Preemptive multitasking

Execution is determined by the operating system, which has the ability to take control from any process and give control to another process. The system allocates time slices to each process fairly and reasonably, and hibernates when the process runs out, or even forces the process to hibernate when the time slice is not running out but more urgent events need to be executed first. This is called time-slice rotation scheduling

Time slice rotation scheduling is one of the oldest, simplest, fairest and most widely used algorithms. Each process is assigned a period of time, called its time slice, which is the amount of time the process is allowed to run. If the process is still running when the time slice ends, the CPU is stripped and allocated to another process. If the process blocks or ends before the time slice ends, the CPU switches immediately. All the scheduler does is maintain a list of ready processes, and when a process runs out of its time slice, it is moved to the end of the queue.

With the experience of process design, threads also enable preemptive multitasking, but they also introduce a new thread safety issue, which is usually addressed through locking, which is not covered here

1.4 Why coroutines are introduced?

As mentioned above in the introduction of processes and threads, processes and threads were introduced to asynchronously execute tasks concurrently, improve system efficiency and resource utilization. But as Java developers, we know how dangerous thread concurrency is, and how difficult it is to write asynchronous code. In Java, asynchronous tasks are typically handled by callbacks, but when asynchronous tasks are nested, programs become complex and difficult to maintain

For example, when we need to complete the requirement: query user information -> find the list of friends of the user -> find the dynamic of the friend, take a look at the Java callback code

getUserInfo(new CallBack() {
    @Override
    public void onSuccess(String user) {
        if(user ! =null) {
            System.out.println(user);
            getFriendList(user, new CallBack() {
                @Override
                public void onSuccess(String friendList) {
                    if(friendList ! =null) {
                        System.out.println(friendList);
                        getFeedList(friendList, new CallBack() {
                            @Override
                            public void onSuccess(String feed) {
                                if(feed ! =null) { System.out.println(feed); }}}); }}}); }}});Copy the code

This is the legendary callback hell, but what if the same requirement was fulfilled with a Kotlin coroutine?

val user = getUserInfo()
val friendList = getFriendList(user)
val feedList = getFeedList(friendList)
Copy the code

Kotlin coroutines, by contrast, are very concise. The core competency of Kotlin coroutines is that they can simplify asynchronous concurrent tasks and write asynchronous code synchronously. That’s why coroutines are introduced: simplify asynchronous concurrent tasks

2. What exactly is a coroutine

2.1 What is a coroutine?

A non-preemptive (collaborative) mode of scheduling tasks in which a program can actively suspend or resume execution.

2.2 What is the difference between coroutines and threads?

Coroutine is based on thread, but it is much lighter than thread. It can be understood as simulating thread operation at user level. Every time a coroutine is created, a kernel-state process is dynamically bound. In user mode, the process is scheduled and switched, and the kernel thread actually executes the task. All context switches of threads require the participation of the kernel, while the context switches of coroutines are completely controlled by users, which avoids a large number of interrupt participation and reduces the resources consumed by thread context switches and scheduling. Threads are an operating system level concept, coroutines are a language level concept

The biggest difference between thread and coroutine is: thread is passive suspend recovery, coroutine is active suspend recovery

2.3 How can coroutines be classified?

According to whether to open up the corresponding function call stack is divided into two categories:

  • Stack coroutine: has its own call stack, can be suspended at any function call hierarchy, and transfer scheduling rights;
  • Stackless coroutines: there is no call stack of its own, and the state of the hanging point is implemented through syntax such as state machines or closures;

2.4 KotlinWhat is the coroutine in?

“Fake” coroutines, Kotlin does not implement a synchronization mechanism (locking) at the language level, and still rely on the Java keywords (such as synchronized) provided by the Kotlin-JVM, that is, the implementation of the lock is left to the thread, so Kotlin coroutines are essentially just a set of encapsulation based on the native Java thread pool.

The core competency of Kotlin coroutines is that they can simplify asynchronous concurrent tasks and write asynchronous code synchronously. Here are some basic concepts in kotin coroutines

3. What is a suspended function?

We know that a function modified using the suspend keyword is called a suspended function, and that a suspended function can only be used inside a coroutine or other suspended function. The point where the suspended function is called inside a coroutine is called the suspend starting point. If an asynchronous call occurs at the suspend starting point, the current coroutine is suspended until the corresponding Continuation’s resume function is called

Let’s look at the details of the execution of the suspend function



As you can see, kotlin coroutines can switch threads in a single line of code

How do you do this, mainly bysuspendThe keyword

3.1 what issuspend

The essence of suspend is CallBack.

suspend fun getUserInfo(a): String {
    withContext(Dispatchers.IO) {
        delay(1000L)}return "BoyCoder"
}
Copy the code

But when we write a suspended function, we don’t write a callback, so where do callbacks come from? Let’s look at the result of decompilation

// A Continuation is equivalent to a CallBack
/ / left
public static final Object getUserInfo(Continuation $completion) {...return "BoyCoder";
}

public interface Continuation<in T> {
    public val context: CoroutineContext
// Equivalent to the onSuccess result
/ / left left
    public fun resumeWith(result: Result<T>)
}
Copy the code

You can see why suspend functions cannot be called outside of a coroutine. You can see why suspend functions cannot be called outside of a coroutine. You can see why suspend functions cannot be called outside of a coroutine. Because of the passing of this Continuation instance

4. What isCPSconversion

Let’s animate the hanging function inCPSChange of function signature during conversion:

There are two main changes you can see

1. IncreasedContinuationParameter of type

2. Return type fromStringInto aAny

The argument changes and we talked about that before, why does the return change?

4.1 Return value of suspended function

After a suspend function is converted by CPS, its return value has an important function: it indicates whether the suspended function has been suspended or not. As strange as this sounds, will the suspended function remain suspended?

All functions that are decorated by Suspend are suspended, but not all suspended functions are suspended. Only if the suspended function contains an asynchronous operation is it actually suspended

Because the suspend modified function, both might return CoroutineSingletons. COROUTINE_SUSPENDED, said hung May also return the result of the synchronous operation, and may even return null in order to fit all the possibilities, If CPS is converted, the return type must be Any? .

4.2 summary

1. Functions modified by suspend are suspended functions 2. Suspended functions are not always suspended when executed 3. A suspended function can only be called from within another suspended function. 4. A suspended function is only truly suspended if it contains an asynchronous operation

5. ContinuationWhat is?

ContinuationEtymology iscontinueWhich is to go on,The next thing to doThe meaning of

Put into the programContinuationIt represents,The code to execute next

As an example of the code above, when the program runsgetUserInfo()When it’s time toContinuationHere is the code in the red box:



ContinuationisThe next code to run, the rest of the unexecuted code.

To understand theContinuationAnd later,CPSIt’s easy to understand, it’s just:A pattern of passing the code that a program will execute next.

CPSTo transform is to take the originalSynchronize suspend functionsConverted toCallBackThe process of asynchronous code.

This conversion is done behind the scenes by the compiler, and we programmers are unaware of it.



Of course, one might ask, is it so simple? Three hang functions eventually become threeCallback?

Of course not. The idea is stillCPSBut it needs to be combined with a state machine

CPSwithThe state machineIs the core of the coroutine implementation

6. The state machine

kotlinThe implementation of coroutines depends on the state machine

To see the implementation, you cankotinDecompile the source code into bytecode to see the compiled code

Bytecode analysis has been done before, and done very well, for reference:Kotlin Jetpack combat | 09. Graphic coroutines principle

Readers can follow the links above for a detailed study, and the following is an animated demonstration of the state machine

  1. The core of the coroutine implementation isCPSTransformation and state machines
  2. The coroutine executes to the suspended function. If a function is suspended, its return value will be:CoroutineSingletons.COROUTINE_SUSPENDED
  3. After the suspended function completes execution, passContinuation.resumeMethod callback, hereContinuationIs through theCPSThe incoming
  4. The incomingContinuationIs, in fact,ContinuationImpl.resumeThe method is going to come backinvokeSuspendIn the method
  5. invokeSuspendA method is where the code we write is executed, multiple times during the course of the coroutine
  6. invokeSuspendThrough the state machine to achieve state flow
  7. continuation.labelIs the key to the state flow,labelA change represents a pending recovery of the coroutine
  8. throughbreak labelimplementationgoToJump effect of
  9. The code that we write in the coroutine is split into states in the state machine and executed separately
  10. After each coroutine switch, an exception is checked
  11. Before switching coroutines, the state store the previous result as a member variable incontinuationIn the.

This is the general flow of the state machine flow, and the reader can follow the reference link to see if the compiled bytecode execution flow is correct

7. CoroutineContextWhat is?

We said above that a Continuation is code that continues to execute, and it is also an interface in implementation

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}
Copy the code

A Continuation consists primarily of two parts, a context and a resumeWith method 2. Execute the following code through the resumeWith method. 3. Get the context resources through the context, saving some of the state and resources at the time of suspension

CoroutineContext is the context, which mainly carries the work of resource acquisition, configuration management and so on. It is the unified provider of common data resources related to the execution environment

CoroutineContext is a special Set that has both the characteristics of a Map and a Set where each Element is an Element, and each Element has a Key that corresponds to it, Elements with the same Key that cannot be repeated can be combined with a + sign. Element has several subclasses, and CoroutineContext is mainly composed of these subclasses:

  • Job: the unique identifier of a coroutine, which controls its life cycle (new,active,completing,completed,cancelling,cancelled);
  • CoroutineDispatcher: specifies the thread on which the coroutine will run (IO,Default,Main,Unconfined);
  • CoroutineName: specifies the name of the coroutine. The default iscoroutine;
  • CoroutineExceptionHandler: Specifies an exception handler for a coroutine to handle uncaught exceptions.

7.1 CoroutineContextData structure of

Let’s look atCoroutineContextThe family of

public interface CoroutineContext {
   
    // The operator [] is overloaded to retrieve the Element associated with the Key by CoroutineContext[Key]
    public operator fun <E : Element> get(key: Key<E>): E?

    // It is an aggregation function that provides the ability to traverse each Element in the CoroutineContext from left to right and do an operation for each Element
    public fun <R> fold(initial: R, operation: (R.Element) - >R): R

    CoroutineContext + CoroutineContext + CoroutineContext + CoroutineContext + CoroutineContext + CoroutineContext + CoroutineContext + CoroutineContext
    public operator fun plus(context: CoroutineContext): CoroutineContext
    
    // Return a new CoroutineContext that removes the Element corresponding to the Key
    public fun minusKey(key: Key< * >): CoroutineContext
  
    //Key definition, empty implementation, just do an identity
    public interface Key<E : Element>

   //Element definition. Each Element is a CoroutineContext
    public interface Element : CoroutineContext {
       
      	// Each Element has a Key instance
        public val key: Key<*>
				
      	/ /...}}Copy the code

1.CoroutineContext is the main store of the Element, which can be valued by a map like [key] 2. The Element also implements the CoroutineContext interface, which seems strange. Mainly for API design convenience,Element will only store itself 3. In addition to the plus method, The other three methods in CoroutineContext are overwritten by CombinedContext, Element, and EmptyCoroutineContext. 4.CombinedContext is an implementation of the CoroutineContext set structure. It’s a recursive definition, where Element is the Element of the CombinedContext, and EmptyCoroutineContext is an EmptyCoroutineContext, which is an empty implementation

7.2 whyCoroutineContextCan be achieved by+No connection

CoroutineContextCan pass+Connection, mainly because of the rewriteplusmethods

When using+When the number is connected, the package is actually arrivedCombinedContextAnd point to the previous oneContext



As shown above, is a single linked list structure, in the acquisition is also through this way to query the correspondingkey, the operation logic is to access the current firstelement, not satisfied, visit againlefttheelement, the order is fromrighttoleft

Because of space, here is not together to see the source code, if you want to see the source code analysis of students can refer to:CoroutineContext’s plus operation

conclusion

This paper mainly focuses on what coroutine is this entry point, introduces some preknowledge about coroutine, what coroutine is, and some basic concepts about kotlin coroutine is, summarized as follows: 1. 2.Kotlin coroutines are essentially just a set of packages based on native Java thread pools. 3. The core competency of Kotlin coroutines is that they can simplify asynchronous concurrent tasks and write asynchronous code synchronously. 4. The implementation of Kotlin coroutines mainly relies on CPS transitions and state machines

Related articles

How does a coroutine switch threads?

The resources

Boring Kotlin coroutines trilogy (on) – concept enlightenment Kotlin article | 09 Jetpack practical experience. CoroutineContext in Kotlin coroutines