bennyhuo / Bennyhuo

bennyhuo.vercel.app
9 stars 3 forks source link

破解 Kotlin 协程(3) - 协程调度篇 | Bennyhuo #17

Open bennyhuo opened 5 years ago

bennyhuo commented 5 years ago

https://www.bennyhuo.com/2019/04/11/coroutine-dispatchers/

上一篇我们知道了协程启动的几种模式,也通过示例认识了 launch 启动协程的使用方法,本文将延续这些内容从调度的角度来进一步为大家揭示协程的奥义。

ZhouGongZaiShi commented 5 years ago

多谢老师分享。

回答一下思考题:拦截器可以有多个吗?
答:拦截器应该只能设置一个吧, 毕竟 ContinuationInterceptorCoroutineContext 集合中的一个 Key 了,后续通过 plus 添加的 ContinuationInterceptor 会覆盖之前的。
如果要实现 OkHttp 那样的多次 Intercept,应该可以在 Continuation 做文章。

bennyhuo commented 5 years ago

@ZhouGongZaiShi 多谢老师分享。

回答一下思考题:拦截器可以有多个吗?
答:拦截器应该只能设置一个吧, 毕竟 ContinuationInterceptorCoroutineContext 集合中的一个 Key 了,后续通过 plus 添加的 ContinuationInterceptor 会覆盖之前的。
如果要实现 OkHttp 那样的多次 Intercept,应该可以在 Continuation 做文章。

是的 一个拦截器,然后再弄一堆出来

SilenceDut commented 5 years ago

有个疑问,Dispatchers.IO有什么特殊的地方,我通过打印的日志看到好像和Deafult是同一个线程池,你上面的文章说并实现了独立的队列和限制,还是不太理解

bennyhuo commented 5 years ago

是同一个线程池,Default 的设计思路实际上是调度一些不怎么重的任务,而 IO 通常比较耗时,因此 IO 在调度的时候会根据需求创建和销毁线程。你可以看下文档,讲的挺清楚的

RubiTree commented 5 years ago

先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲 CombinedContext 时不是很明白,为什么要定义成 left: CoroutineContext + element: Element 这样的数据结构呢?CoroutineContext 本身不就能实现类似 Combined 的功能嘛?也没见过 Map 里有 CombinedMap 呀。一点粗浅的问题,望大佬解惑~

RubiTree commented 5 years ago

另外,1.1中这句「这里的 Job 实际上是对它的 companion object 的引用」也没理解,Job 不是下面这个接口的类型吗?怎么变成了是对它的 companion object 的引用呢?

RubiTree commented 5 years ago

还有,这句话「拦截器也是一个上下文的实现方向,拦截器可以左右你的协程的执行,同时为了保证它的功能的正确性,协程上下文集合永远将它放在最后面 ,这真可谓是天选之子了」没太理解,为什么协程上下文集合永远将它放在最后面就是天选之子呢?请大佬指点 🤔

bennyhuo commented 5 years ago

@RubiTree 先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲 CombinedContext 时不是很明白,为什么要定义成 left: CoroutineContext + element: Element 这样的数据结构呢?CoroutineContext 本身不就能实现类似 Combined 的功能嘛?也没见过 Map 里有 CombinedMap 呀。一点粗浅的问题,望大佬解惑~

这里说的是函数式的递归 List 定义。CoroutineContext 的递归定义在 CombinedContext 中体现,这个写法跟 scala 的 List 如出一辙。函数式语言不能迭代,所有的循环、遍历都是靠递归,因此每一个 List 本质上就是头元素和剩下的 List 部分,遍历的时候访问了第一个,以后再递归的访问剩下的 List,这样就可以实现遍历。CoroutineContext 这么定义的用意也正是如此。

bennyhuo commented 5 years ago

@RubiTree 另外,1.1中这句「这里的 Job 实际上是对它的 companion object 的引用」也没理解,Job 不是下面这个接口的类型吗?怎么变成了是对它的 companion object 的引用呢?

Job 是一个接口,没错。但它还有一个 companion object,如果没有明确定义名称的话,我们可以直接用接口名来引用 companion object。去看下相关的定义很简单的。

bennyhuo commented 5 years ago

@RubiTree 还有,这句话「拦截器也是一个上下文的实现方向,拦截器可以左右你的协程的执行,同时为了保证它的功能的正确性,协程上下文集合永远将它放在最后面 ,这真可谓是天选之子了」没太理解,为什么协程上下文集合永远将它放在最后面就是天选之子呢?请大佬指点 🤔

因为 CoroutineContext 的递归定义的特性,它与通常意义的递归定义正好反着来了,所以它实际上是从最后一个元素开始遍历的,任何时候对它的遍历都是最先访问最后一个元素,换言之访问最后一个元素的查找时间是 O(1),而对于整个上下文集合的查找,时间复杂度是 O(n),把拦截器放到最后,就是为了更快的拿到拦截器实例。为什么需要更快的拿到拦截器?因为协程每次挂起后恢复时都会读取拦截器来处理拦截逻辑,因此拦截器的访问是非常高频的,因此这实际上是一个性能优化。

RubiTree commented 4 years ago

@enbandari

@RubiTree 先感谢群主大佬辛苦分享文章哈~ 然后有个小问题想问,1.1中借「scala 的 List」讲 CombinedContext 时不是很明白,为什么要定义成 left: CoroutineContext + element: Element 这样的数据结构呢?CoroutineContext 本身不就能实现类似 Combined 的功能嘛?也没见过 Map 里有 CombinedMap 呀。一点粗浅的问题,望大佬解惑~

这里说的是函数式的递归 List 定义。CoroutineContext 的递归定义在 CombinedContext 中体现,这个写法跟 scala 的 List 如出一辙。函数式语言不能迭代,所有的循环、遍历都是靠递归,因此每一个 List 本质上就是头元素和剩下的 List 部分,遍历的时候访问了第一个,以后再递归的访问剩下的 List,这样就可以实现遍历。CoroutineContext 这么定义的用意也正是如此。

感谢大佬解答!所以协程因为也是不希望使用迭代来遍历,所以采用这样的数据结构吧

guodongAndroid commented 3 years ago

您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30

第一种情况:两个拦截器的Key都是ContinuationInterceptor

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

14:57:49:039 [main] 1
14:57:49:100 [main] 2
14:57:49:116 [main] 4
14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit)
14:57:50:117 [MyMain2] 3
14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9
14:57:50:118 [MyMain2] 5

这种情况我是按后添加的拦截器会覆盖之前的拦截器,Key是一样的

第二种情况:两个拦截器的Key都是自定义的

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

15:05:51:374 [main] 1
15:05:51:433 [main] 2
15:05:51:451 [main] 4
15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit)
15:05:52:452 [MyMain1] 3
15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9
15:05:52:453 [MyMain1] 5

这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~

CoenQian commented 2 years ago

@guodongAndroid 您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30

第一种情况:两个拦截器的Key都是ContinuationInterceptor

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

14:57:49:039 [main] 1
14:57:49:100 [main] 2
14:57:49:116 [main] 4
14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit)
14:57:50:117 [MyMain2] 3
14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9
14:57:50:118 [MyMain2] 5

这种情况我是按后添加的拦截器会覆盖之前的拦截器,Key是一样的

第二种情况:两个拦截器的Key都是自定义的

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

15:05:51:374 [main] 1
15:05:51:433 [main] 2
15:05:51:451 [main] 4
15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit)
15:05:52:452 [MyMain1] 3
15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9
15:05:52:453 [MyMain1] 5

这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~

因为 CoroutineContext 的 plus 只处理 KEY 是 ContinuationInterceptor 的拦截器

bennyhuo commented 2 years ago

@guodongAndroid 您好,感谢大佬辛苦分享文章~,然后我创建了两个自定义拦截做了个试验,产生了不同的结果,希望可以解惑下: 基于kotlin 1.4.30

第一种情况:两个拦截器的Key都是ContinuationInterceptor

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

    // companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = ContinuationInterceptor

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

14:57:49:039 [main] 1
14:57:49:100 [main] 2
14:57:49:116 [main] 4
14:57:50:115 [kotlinx.coroutines.DefaultExecutor] <MyContinuation2> Success(kotlin.Unit)
14:57:50:117 [MyMain2] 3
14:57:50:117 [MyMain2] com.guodong.test.coroutine.MyNewContinuationInterceptor2@2cc55ab9
14:57:50:118 [MyMain2] 5

这种情况我是按后添加的拦截器会覆盖之前的拦截器,Key是一样的

第二种情况:两个拦截器的Key都是自定义的

suspend fun main() {

    log(1)

    val job = GlobalScope.launch(
        context = CoroutineName("Hello") + MyNewContinuationInterceptor1() + MyNewContinuationInterceptor2(),
        start = CoroutineStart.UNDISPATCHED
    ) {
        log(2)
        delay(1000)
        log(3)
        log(coroutineContext[ContinuationInterceptor])
    }

    log(4)

    job.join()

    log(5)
}

private class MyNewContinuationInterceptor1 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor1>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor1

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation1> $result")
                thread(name = "MyMain1") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

private class MyNewContinuationInterceptor2 : ContinuationInterceptor {

     companion object Key : CoroutineContext.Key<MyNewContinuationInterceptor2>

    override val key: CoroutineContext.Key<*> = MyNewContinuationInterceptor2

    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        return object : Continuation<T> {
            override val context: CoroutineContext = continuation.context

            override fun resumeWith(result: Result<T>) {
                log("<MyContinuation2> $result")
                thread(name = "MyMain2") {
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

输出如下:

15:05:51:374 [main] 1
15:05:51:433 [main] 2
15:05:51:451 [main] 4
15:05:52:450 [kotlinx.coroutines.DefaultExecutor] <MyContinuation1> Success(kotlin.Unit)
15:05:52:452 [MyMain1] 3
15:05:52:452 [MyMain1] com.guodong.test.coroutine.MyNewContinuationInterceptor1@2cc55ab9
15:05:52:453 [MyMain1] 5

这种情况为什么会走第一个拦截器,是第二个拦截器根本没有调用?望大佬解惑~

因为 CoroutineContext 的 plus 只处理 KEY 是 ContinuationInterceptor 的拦截器

上下文当中的key只能对应一个,这跟map是类似的。拦截器自然只会有一个生效。

你也可以看一下这期视频来了解更多:【[Kotlin 协程] 给协程的拦截器自定义 Key 有什么后果?-哔哩哔哩】 https://b23.tv/wZsWxEI

CoenQian commented 2 years ago
suspend fun main() {
    log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程
    val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
    GlobalScope.launch(myDispatcher) {
        log("2")
    }.join()
    log("3")
}
[main] 1
[MyThread] 2
[main] 3

bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。

bennyhuo commented 2 years ago
suspend fun main() {
    log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程
    val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
    GlobalScope.launch(myDispatcher) {
        log("2")
    }.join()
    log("3")
}
[main] 1
[MyThread] 2
[main] 3

bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。

这个其实不是一定的。3在不在主线程,完全取决于join是不是会挂起。

CoenQian commented 2 years ago

@bennyhuo

suspend fun main() {
    log("1") // 这里加上一句,log 3 就在 main 线程了,去掉的话,log 3 是 MyThread 线程
    val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
    GlobalScope.launch(myDispatcher) {
        log("2")
    }.join()
    log("3")
}
[main] 1
[MyThread] 2
[main] 3

bennyhuo 你好,这里有点不理解,为什么去掉 log 1 的话,log 3 就去 myDispatcher 里调度了。

这个其实不是一定的。3在不在主线程,完全取决于join是不是会挂起。

感谢点拨,如果 join 挂起了,log(3) 所在的协程续体,由于没定义拦截器就会在 log(2) 的拦截器定义的线程里 resume

suspend fun main() {
    log("1")
    val myDispatcher= Executors.newSingleThreadExecutor{ r -> Thread(r, "MyThread") }.asCoroutineDispatcher()
    GlobalScope.launch(myDispatcher) {
        log("2")
        delay(100)
    }.join()
    log("3") // log 3 始终是 MyThread 线程
}
likai79511 commented 1 year ago

关于: 使用自定义dispatcher(使用10个线程的线程池)例子中, delay 的前后 线程切换了。 但是我将自定义的dispatcher换成 Dispatcher.IO 或者 default, 发现 delay前后,线程并没有切换。 这个是为什么? IO 和 default 背后 也是 >1 的线程池啊

bennyhuo commented 1 year ago

大于1,也可以调度到之前的线程,合理啊

---原始邮件--- 发件人: "Agera @.> 发送时间: 2023年4月27日(周四) 上午9:39 收件人: @.>; 抄送: "Benny @.**@.>; 主题: Re: [bennyhuo/Bennyhuo] 破解 Kotlin 协程(3) - 协程调度篇 | Bennyhuo (#17)

关于: 使用自定义dispatcher(使用10个线程的线程池)例子中, delay 的前后 线程切换了。 但是我将自定义的dispatcher换成 Dispatcher.IO 或者 default, 发现 delay前后,线程并没有切换。 这个是为什么? IO 和 default 背后 也是 >1 的线程池啊

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

likai79511 commented 1 year ago
fun main() = runBlocking {
    e(0)
    coroutineScope {
        launch(Dispatchers.IO) {
            e(1)
            delay(100)
            e(2)
        }
    }
    e(3)
}

贴上一个打印数据:

---[main-1]---0
---[DefaultDispatcher-worker-1-13]---1
---[DefaultDispatcher-worker-1-13]---2
---[main-1]---3

像这个例子, 我运行了很多次, 1 和 2 都处于同一个线程。 Dispatchers.IO 背后的线程池的线程数 没记错的话 默认是64, 为什么 delay 前后还在同一个线程中呢?

我看老师的例子中,如果自己实现这个dispatcher (用10 个线程的线程池), delay前后 有可能分别在不同的线程。

但是如果我将此处的 Dispatcher.IO 换成是自己实现的 newFixedThreadPool(10).asCoroutineDispatcher() 后, delay 前后 又在不同的线程了, 比如下面:

fun main() = runBlocking {
    e(0)
    val dis = Executors.newFixedThreadPool(10).asCoroutineDispatcher()
    coroutineScope {
        launch(dis) {
            e(1)
            delay(100)
            e(2)
        }
    }
    e(3)
}

打印如下:

---[main-1]---0
---[pool-1-thread-1-13]---1
---[pool-1-thread-2-15]---2
---[main-1]---3
likai79511 commented 1 year ago

IO 仅在 Jvm 上有定义,它基于 Default 调度器背后的线程池,并实现了独立的队列和限制,因此协程调度器从 Default 切换到 IO 并不会触发线程切换。

下面这个 从 Default 切到 IO,线程是发生了变化啊

fun main() = runBlocking {
    e(0)
    coroutineScope {
        launch(Dispatchers.Default) {
            e(1)
            launch(Dispatchers.IO) {
                e(2)
            }
        }
    }
    e(3)
}
---[main-1]---0
---[DefaultDispatcher-worker-1-13]---1
---[DefaultDispatcher-worker-2-14]---2
---[main-1]---3
bennyhuo commented 1 year ago

这是内部的调度机制决定的,如果想要了解细节,建议直接看源码。理论上,一直调度到一个线程也是不违反设计的,另外,并没有 64 个线程的说法,应该是最多使用 64 个线程处理并发调度(实际上要受限于线程池的大小)。

猜测,这里始终使用一个线程,可能跟线程池比较闲有关系,你可以试试同时调度点儿别的任务。

bennyhuo commented 1 year ago

另外需要说明一点的是,文章是基于 Kotlin 1.3 撰写的,版本变化可能会带来结果的差异,是正常的。