1. Introduction

Perhaps the most common statement you hear about coroutines is that they are lightweight threads. You look confused, don’t you? This is the official slogan. Strictly speaking, on the one hand, the official slogan is to make people associate coroutines with threads intuitively. On the other hand, it wants to advertise that coroutines are better than threads in performance and fully convince people to use it. In this article I will try to clarify what a coroutine is.

2. Talk about threads

Since “coroutines are lightweight threads”. So it’s worth remembering what is a thread? In a generic Java application, it’s too easy to start a Thread. New a Thread, override the run method, call the start method, and that’s it.

public fun thread( start: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, name: String? = null, priority: Int = -1, block: () -> Unit ): Thread { val thread = object : Thread() { public override fun run() { block() } } if (isDaemon) thread.isDaemon = true if (priority > 0) thread.priority = priority if (name ! = null) thread.name = name if (contextClassLoader ! = null) thread.contextClassLoader = contextClassLoader if (start) thread.start() return thread }Copy the code

Simplicity is simple, but it has its drawbacks:

  1. If the number of threads created exceeds the maximum number of file descriptors, the application will report OOM (when the speed of threads created > the speed of threads consumed).

  2. If threads need to be created frequently to execute very short code, frequent thread switching can also have an impact on performance

  3. The communication between threads is complicated, and it is not easy to transfer data from thread A to thread B

Because of these drawbacks, we have thread pools.

3. Talk about thread pools

Since the focus of this article is on coroutines, if you are not familiar with thread pools, you can fill in the blanks.

Thread pools are familiar to most of you. Cache pooling, object pooling, connection pooling, all sorts of pool-related technologies are caching technologies. The object cached by the thread pool is the thread.

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
Copy the code

We can see several core parameters of the thread pool:

  1. CorePoolSize Number of core thread pools
  2. MaximumPoolSize Maximum number of thread pools
  3. BlockingQueue

    workQueue workQueue, which holds Runnable objects

It inherits from Thread. Its run method calls the runWorker method. The source code is as follows:

//ThreadPoolExecutor.java final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task ! = null || (task = getTask()) ! = null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && ! wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); }}Copy the code

We see that this method basically loops through the executable runnable from the workQueue to execute. If the condition of the while loop is not true, then the thread will exit. This is a mistake. Since workQueue is BlockingQueue, if there are no Runnable objects in the queue, the code will block and cannot jump out of the loop.

So back to the beginning of the article, “Coroutines are lightweight threads,” what is even lighter than threads. By the way, as smart readers might have guessed, runnable in workQueue. For ease of understanding, we can think of coroutines as the smallest unit of thread execution, the Runnable in the work queue, as shown in the source code.

AbstractCoroutine public abstract class AbstractCoroutine<in T> (...) : JobSupport(active), Job, Continuation<T>, CoroutineScope //2. DispatchedContinuation internal class DispatchedContinuation<in T>( @JvmField val dispatcher: CoroutineDispatcher, @JvmField val continuation: Continuation<T> ) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation //3. DispatchedTask internal abstract class DispatchedTask<in T>( @JvmField public var resumeMode: Int ) : SchedulerTask() internal actual typealias SchedulerTask = Task //4.Task internal abstract class Task( @JvmField var submissionTime: Long, @JvmField var taskContext: TaskContext ) : Runnable { constructor() : this(0, NonBlockingContext) inline val mode: Int get() = taskContext.taskMode // TASK_XXX }Copy the code

The above source code can also be simplified as

public abstract class AbstractCoroutine<in T> (...) : RunnableCopy the code

A coroutine is simply a Runnable, and the Runnable must be stored in a work queue to take advantage of its lightness.

Digression: Threads, loops, queues, MessageQueue. Doesn’t MainThread, which Android developers are most familiar with, naturally satisfy these features? Is Posting a Runnable to a Handler also starting a coroutine? If that analogy makes it easier for you to understand coroutines, then so be it, and that’s fine. It’s just that coroutines can do a lot more than post a thread to the main thread in minimal units.

Since the thread pool, MainThread has taken full advantage of the thread performance. So why do we have coroutines? What problem does the coroutine solve above them?

Talk about coroutines

Let’s start with the simplest example. Start a coroutine in your Activity, sleep for 10 seconds in the child thread, and then print out the value returned by the child thread in the main thread.

//TestActivity.java MainScope().launch { val result = withContext(Dispatchers.IO) { Thread.sleep(10_000) println("I am running in ${Thread.currentThread()}") "Hello coroutines" } println("I am running in ${Thread.currentThread()} result is  $result") }Copy the code

The printable result is as follows. We see the value returned by sleeping in the child thread and printing in the main thread.

The 2021-11-22 22:29:02. 868, 3407-3463 / com. Peter. Viewgrouptutorial I/System. Out: I am running Thread in [DefaultDispatcher - worker - 1, 5, the main] 22:29:02. 2021-11-22, 874, 3407-3407 / com. Peter. Viewgrouptutorial I/System.out: I am running in Thread[main,5,main] result is Hello coroutinesCopy the code

At first glance, you may have a question, brother, to achieve this demand, is it necessary to be so complicated, brother, I three times five divide two do it? Look at me:

thread {
    Thread.sleep(10_000)
    println("I am running in ${Thread.currentThread()}")
    val result = "Hello coroutines"
    Handler(Looper.getMainLooper()).post {
        println("I am running in ${Thread.currentThread()} result is $result")
    }
}
Copy the code

Easy to do a few lines of code, steady and elegant, print exactly the same result.

The 2021-11-22 22:35:59. 016, 3597-3655 / com. Peter. Viewgrouptutorial I/System. Out: I am running in the Thread/Thread - 3, 5, the main 22:35:59. 2021-11-22, 020, 3597-3597 / com. Peter. Viewgrouptutorial I/System. Out: I am running in Thread[main,5,main] result is Hello coroutinesCopy the code

So the question is, what if the requirement is to sleep 10 seconds in the child thread and return the value to another child thread? Of course, it’s not impossible to do with traditional threads, but with coroutines it’s pretty easy

// To simulate the effect, Use only one thread thread pool to specially when the Dispatcher MainScope () launch (Executors. NewFixedThreadPool (1) asCoroutineDispatcher ()) {val result = withContext(Dispatchers.IO) { Thread.sleep(10_000) println("I am running in ${Thread.currentThread()}") "Hello coroutines" } println("I am running in ${Thread.currentThread()} result is $result") }Copy the code

The output is as follows. Note that there are two different threads:

The 2021-11-22 22:41:01. 953, 3872-3927 / com. Peter. Viewgrouptutorial I/System. Out: I am running Thread in [DefaultDispatcher - worker - 1, 5, the main] 22:41:01. 2021-11-22, 960, 3872-3926 / com. Peter. Viewgrouptutorial I/ system. out: I am running in Thread[pool-1 thread-1,5,main] result is Hello coroutinesCopy the code

5. To summarize

So in my opinion, coroutines have the following properties:

  1. The coroutine body is encapsulated as a Runnable, the smallest unit that a thread can execute, which is precisely the Continuation in the coroutine, and is distributed to the corresponding thread’s corresponding work queue through a distribution mechanism
  2. Continuations hold data in coroutine stack frames, carry them with you when you switch threads, and bring data back through them when you cut back. (Yes, like callback)
  3. A thread pool encapsulates a thread for the developer and simply submits a Runnable to the pool. While the coroutine encapsulates threads and callbacks for developers, the developer does not need to care about the internal logic of threads and thread switching.
//TestActivity.java MainScope().launch { val result = withContext(Dispatchers.IO) { Thread.sleep(10_000) println("I am running in ${Thread.currentThread()}") "Hello coroutines" } println("I am running in ${Thread.currentThread()} result is  $result") }Copy the code
val coroutinesBodyRunnable = java.lang.Runnable { thread { Thread.sleep(10_000) println("I am running in ${Thread.currentThread()}") val result = "Hello coroutines" Handler(Looper.getMainLooper()).post { println("I am running  in ${Thread.currentThread()} result is $result") } } } Handler(Looper.getMainLooper()).post(coroutinesBodyRunnable)Copy the code

The above code is equivalent. Time reasons, specific principles, the subsequent talk, please look forward to. If you find this article helpful, please share it with your friends. I hope we can make more sparks in the comments, discuss the technology together and make progress together.

Follow bytecolet’s official account and send a Kotlin Coroutines ebook. This book is the best introduction to coroutines available so far. The best, none of them.