preface

I have been reading The Kotlin coroutine recently. Since I wrote Golang before, I tried to compare it and found a lot of interesting things.

A small example from Kotlin

Ask the following code, what is the result of execution?

fun main(a) = runBlocking {
    Create a custom thread pool
    val coroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
    val name = Thread.currentThread().name
    println("main start thread-id = $name")
    for (i in 0.2.) {
        launch(coroutineDispatcher) {
            while (true) {
                val j = i + 1
                val filename = "/Users/xxxx/Desktop/mapping$j.txt"
                val file = File(filename)
                val contents = file.readText()
                val name = Thread.currentThread().name
                println("thread-id = $name, do  work $i")
            }
        }
    }
    println("main end thread-id = $name")}Copy the code

This code starts 3 coroutines without runBlocking and executes in a single thread pool in an infinite loop, reading mapping.txt(a file about 30M in size) in an infinite loop.

My machine is a Mac

Kotlin version numbers are as follows: Kotlinx-Coroutines-core: 1.4.2 Kotlin-stdlib: 1.4.0

The results are as follows:

main start thread-id = main main end thread-id = main thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, Do work 0 thread-id = pool-1-thread-1, do work 0 thread-id = pool-1-thread-1, do work 0Copy the code

It can be seen that the carrier of coroutine is thread, and the code logic of coroutine can be understood as a task. In the case of multi-threading and multi-task and no special blocking task (infinite loop, etc.), the task can be executed sooner or later.

However, this logic is an infinite loop and a single thread, so that when I = 0, the created coroutine task occupies the current thread, and the second coroutine task cannot be executed.

So it can be seen that Kotlin’s coroutine task will not be executed because it is non-preemptive (the coroutine is starved to death).

A small example of Golang

Ask the following code, what is the result of execution?

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"syscall"
	"time"
)

func main(a) {
	runtime.GOMAXPROCS(1)
	for i := 0; i < 3; i++ {
		go func(j int) {
			for {
				file, err := os.Open(fmt.Sprint("/Users/xxxx/Desktop/mapping", (j + 1), ".txt"))
				iferr ! =nil {
					panic(err)
				}
				defer file.Close()
				ioutil.ReadAll(file)
				tid := gettid()
				fmt.Printf("thread-id = %d, do work %d\n", tid, j)
			}
		}(i)
	}
	time.Sleep(1 * time.Second)
	fmt.Println("finish work")}func gettid(a) (n uint64) {
	r0, _, _ := syscall.RawSyscall(syscall.SYS_THREAD_SELFID, 0.0.0)
	n = uint64(r0)
	return n
}

Copy the code

GOMAXPROCS(1) sets P to one, and Golang’s go keyword starts one coroutine. The rest of the logic is the same as Kotlin’s. Is the result like Kotlin, blocking on the first task?

The results are as follows:

thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 0
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 1
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 2
thread-id = 2873233, do work 0
thread-id = 2873233, do work 0
thread-id = 2873233, do work 1
thread-id = 2873233, do work 1
finish work
Copy the code

Isn’t it amazing? The results are inconsistent! It doesn’t get stuck on the first task because of an infinite loop, and after 1s the main thread terminates and the program exits (because in the code, the main thread only sleeps for 1s).

The thread ID printed by gettid() shows that the three coroutines are running on the same thread, breaking the loop

Gettid episode

Since the computer is MacOSX, Golang used syscall.gettid () to Gettid at first, but found an error: undefined Gettid(). It seems that MacOSX did not implement this method, so I asked my brother to get another method

func gettid(a) (n uint64) {
	r0, _, _ := syscall.RawSyscall(syscall.SYS_THREAD_SELFID, 0.0.0)
	n = uint64(r0)
	return n
}
Copy the code

Questions and Conclusions

conclusion

First, it is confirmed that Kotlin’s coroutine does not seem to have task scheduling optimization, but only executes tasks in the thread pool, and there is coroutine starvation.

Second, Golang’s coroutines have some magical optimizations that allow tasks to be executed alternately, even in an infinite loop.

doubt

At first I thought it was because my go version was higher, because preemptive scheduling was added to GO1.14. I thought it was because of this. It turns out that the above example is consistent across GO1.13 and even go1.10, so what’s the real reason here? Probably netpoller.