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 the
It’s hard to get inwhy
andWhat do I do
The stage
This article mainly answers this question, mainly includes the following content
1. Some pre-knowledge about coroutines
2. What is a coroutine?
3.kotlin
Some basic concepts of coroutines, hang functions,CPS
Transitions, 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:
- A single process can only do one thing, and the code in the process is still executed sequentially.
- 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.
- 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, yes
CPU
The 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 Kotlin
What 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 bysuspend
The 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 isCPS
conversion
Let’s animate the hanging function inCPS
Change of function signature during conversion:
There are two main changes you can see
1. IncreasedContinuation
Parameter of type
2. Return type fromString
Into 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. Continuation
What is?
Continuation
Etymology iscontinue
Which is to go on,The next thing to do
The meaning of
Put into the programContinuation
It represents,The code to execute next
As an example of the code above, when the program runsgetUserInfo()
When it’s time toContinuation
Here is the code in the red box:Continuation
isThe next code to run, the rest of the unexecuted code
.
To understand theContinuation
And later,CPS
It’s easy to understand, it’s just:A pattern of passing the code that a program will execute next
.
而CPS
To transform is to take the originalSynchronize suspend functions
Converted toCallBack
The 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 stillCPS
But it needs to be combined with a state machineCPS
withThe state machine
Is the core of the coroutine implementation
6. The state machine
kotlin
The implementation of coroutines depends on the state machine
To see the implementation, you cankotin
Decompile 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
- The core of the coroutine implementation is
CPS
Transformation and state machines - The coroutine executes to the suspended function. If a function is suspended, its return value will be:
CoroutineSingletons.COROUTINE_SUSPENDED
- After the suspended function completes execution, pass
Continuation.resume
Method callback, hereContinuation
Is through theCPS
The incoming - The incoming
Continuation
Is, in fact,ContinuationImpl
.resume
The method is going to come backinvokeSuspend
In the method invokeSuspend
A method is where the code we write is executed, multiple times during the course of the coroutineinvokeSuspend
Through the state machine to achieve state flowcontinuation.label
Is the key to the state flow,label
A change represents a pending recovery of the coroutine- through
break label
implementationgoTo
Jump effect of - The code that we write in the coroutine is split into states in the state machine and executed separately
- After each coroutine switch, an exception is checked
- Before switching coroutines, the state store the previous result as a member variable in
continuation
In 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. CoroutineContext
What 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 CoroutineContext
Data structure of
Let’s look atCoroutineContext
The 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 whyCoroutineContext
Can be achieved by+
No connection
CoroutineContext
Can pass+
Connection, mainly because of the rewriteplus
methods
When using+
When the number is connected, the package is actually arrivedCombinedContext
And 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 againleft
theelement
, the order is fromright
toleft
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