V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
insomnia1232
V2EX  ›  Kotlin

Kotlin 的协程实现的意义是什么呢?

  •  
  •   insomnia1232 · 2020-03-20 23:30:14 +08:00 · 7309 次点击
    这是一个创建于 1716 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看了 kotlin 的协程实现,感觉就是线程调度库,换句话说就是伪协程,那和 Rxjava 这种东西的区别在哪呢?是切换效率更高,写起来更简洁吗?还是就因为是官方钦定的

    31 条回复    2020-03-23 18:45:05 +08:00
    LosLord
        1
    LosLord  
       2020-03-20 23:40:55 +08:00
    为了让 Callback 看起来不反人类
    aguesuka
        2
    aguesuka  
       2020-03-21 00:06:07 +08:00
    java:
    read(result-> dosomething());

    kotlin:
    val result = read();
    doSomething();

    java 没法在语言级别做到这样写异步代码
    winterbells
        3
    winterbells  
       2020-03-21 00:15:44 +08:00 via Android
    用了 kotlin 后,rxjava 库已经移除了
    val resp = getFromServer()
    resp.xxx

    不需要走 callback 了,减少嵌套
    xcstream
        4
    xcstream  
       2020-03-21 00:57:42 +08:00
    代码上减少嵌套
    Kotlin 还可以转译成非 java 的语言
    Tyanboot
        5
    Tyanboot  
       2020-03-21 03:11:18 +08:00
    如果这个算伪协程的话, 那真协程应该是什么样子, 或者说具有什么特性才算是真协程呢.
    abcbuzhiming
        6
    abcbuzhiming  
       2020-03-21 08:00:54 +08:00   ❤️ 2
    @Tyanboot 协程的定义是“用户级别的多线程”,而传统的“多线程”是有内核态参与的系统级别实现的多线程,这种实现的线程的建立成本不低,因为内核资源宝贵,并且切换线程的时候有所谓的线程上下文切换开销,用户态内核态切换开销。而协程只在用户态,所以新建协程的成本很低,一台机器上可以建远远比线程上限数量高的多的协程,并且因为只在用户态,协程的切换成本也很低。虽然协程的任务最终还是交给系统线程完成的,但是协程的发明是语言技术进步而对内核技术的一次瘦身,多线程技术有内核态参与这个问题本质上是早期的语言性能不够,为了性能,让内核具备了多线程调度的能力,提供了一种通用的多线程编程模型,且性能不错,但是随着时间语言技术开始进步,慢慢的就有人觉得内核参与的线程成本太高了,而用户态这边的硬件和软件资源突飞猛进,有了超越内核态的可能性,于是就有人希望把多线程再次移动回用户态,这本质是技术发展的一种历史轮回
    PDX
        7
    PDX  
       2020-03-21 08:40:59 +08:00
    怎么看出是“伪协程”的??
    reus
        8
    reus  
       2020-03-21 08:52:41 +08:00   ❤️ 2
    @abcbuzhiming 你对协程的定义是错误的,协程就是“协作式调度的过程”,和它相对的是“抢占式调度的过程”,和用户态内核态没有关系。协作式调度是早于抢占式调度出现的,不存在什么“协程的发明”。基于时钟中断的操作系统线程,在内核看来,也是协作式的,只不过在用户看来是抢占式。
    用户态线程就是用户态线程,不要和协程混为一谈。
    micean
        9
    micean  
       2020-03-21 09:28:28 +08:00
    1. 让代码看得更舒服
    2. 异常处理更舒服
    3. 逻辑处理更舒服

    java 的 vertx 这么写异步

    ```
    Promise.promise(p ->
    异步 A(p)
    ).compose(结果 A -> {
    if(逻辑 A){
    return Promise.promise(p -> 异步 B(结果 A))
    }else{
    return Promise.promise(p -> 异步 C(结果 A)).compose(结果 B -> Promise.promise(p -> 异步 D(结果 B)))
    }
    }).map(结果 C ->
    结果 D
    ).setHandler(最终结果 -> {
    if(没有异常){
    返回结果
    }else{
    处理异常
    }
    })
    ```

    同步代码这么写

    ```
    try{
    结果 A = 异步 A()

    if(逻辑 A){
    结果 C = 异步 B(结果 A)
    }else{
    结果 B = 异步 C(结果 A)
    结果 C = 异步 D(结果 B)
    }

    return (结果 D)结果 C
    }catch(e){
    处理异常
    }
    ```
    wancaibida
        10
    wancaibida  
       2020-03-21 09:35:15 +08:00 via iPhone
    为了以更直观的方式写异步
    hyyou2010
        11
    hyyou2010  
       2020-03-21 09:51:54 +08:00
    我没有读过协程的内部实现,我的理解是:启动协程时可以新启线程,但是协程切换可以在同一个线程内,且仅在用户态完成,这就算真协程,非常高效,这就比需要切换线程的方案强。
    crella
        12
    crella  
       2020-03-21 09:54:10 +08:00
    如果我理解得没有错的话,ruby 的 Fiber 也是伪协程,只是方便不同函数的让出 /切换。在不支持 fork 的系统上,支持并发的只有 Thread.new 。
    newmlp
        13
    newmlp  
       2020-03-21 10:05:56 +08:00
    协程就是在用户空间实现的线程啊,不然你觉得协程是啥
    sukaidev
        14
    sukaidev  
       2020-03-21 10:19:43 +08:00
    kotlin 既然是 JVM 语言 自然跳脱不出 JVM
    协程就是为了能够像平时写同步代码一样写异步代码
    如果说 rxjava 是方便的线程切换 那协程就是感觉不到线程在切换 甚至不需要切换线程同样做到了“异步”
    janxin
        15
    janxin  
       2020-03-21 10:41:02 +08:00 via iPhone
    自然是为了不反人类…这是协程存在的意义之一
    codehz
        16
    codehz  
       2020-03-21 11:48:23 +08:00 via Android
    协程的意义是可以以一致的方法写同步或者异步的调用,而不需要大规模改变写法(比如变成一堆回调)
    准确说它并没有提升性能的意思在里面,也不是什么银弹,不可能把原本做不到异步的东西变成可以异步的,比如 linux 原本没有提供不开线程的异步文件 io,那你也不可能通过协程变成可以不开额外线程的异步读写文件
    因此从这个意义上说,协程就是语言提供的一种机制,简化异步代码的编写
    关于和线程的对比,其实是异步 vs 同步的对比,然后协程可以让异步代码变得看起来像同步的一样,仅此而已
    更一般地说,协程也未必真的要为了异步,也可以用作更一般化的逻辑解构,不过那就是另一个故事了
    hhhsuan
        17
    hhhsuan  
       2020-03-21 12:12:43 +08:00
    如果不用切换线程就实现任务的调度那就是真协程,但 kotlin 给我的感觉是任务的调度还是通过线程切换完成的。
    wanglufei
        18
    wanglufei  
       2020-03-21 12:25:04 +08:00 via Android
    callback 的语法糖
    qiyuey
        19
    qiyuey  
       2020-03-21 12:38:40 +08:00
    @reus 现阶段协程可以理解为:用户态协作式线程;广义和狭义的区别,没必要抠字眼
    qiyuey
        20
    qiyuey  
       2020-03-21 12:41:16 +08:00
    可以从多个层面理解:
    1、是否已经清楚 阻塞 和 非阻塞 的区别
    2、是否已经清楚 Callback 的问题
    3、是否已经清楚 Reactive 和 Coroutines 的区别
    这三个问题是逐层递进的,需要一个一个理解
    reus
        21
    reus  
       2020-03-21 12:44:39 +08:00
    @qiyuey 这是字眼上的区别???不同概念就是不同概念,自己没搞清楚就说别人抠字眼???不存在什么广义协程协程,只有正确理解和错误理解。
    no1xsyzy
        22
    no1xsyzy  
       2020-03-21 13:26:31 +08:00
    @abcbuzhiming 查了下,“用户态的轻量级多线程” 应该叫 “纤程” 而不是 “协程”,协程和线程是相互正交的两种概念。
    yule111222
        23
    yule111222  
       2020-03-21 13:43:10 +08:00
    就是写起来简单点,确实是伪协程
    no1xsyzy
        24
    no1xsyzy  
       2020-03-21 13:44:34 +08:00
    协程的定义是用 yield 来主动交出控制权,具体的执行器到底有几个是不确定的。

    引入协程式语法并不需要线程调度,转换 CSP 之后 yield 实质上把续延当作 callback 传递不就行了?写还是写类似同步的。
    不过既然没有引入续延那就是这样封装一把容易(得多得多)。
    sagaxu
        25
    sagaxu  
       2020-03-21 13:50:46 +08:00 via Android
    @hhhsuan 你的感觉是错的
    qiyuey
        26
    qiyuey  
       2020-03-21 15:19:05 +08:00
    @hhhsuan 你的感觉并不对
    hhhsuan
        27
    hhhsuan  
       2020-03-21 15:29:33 +08:00
    @sagaxu #25
    @qiyuey #26
    show me the code
    Tyanboot
        28
    Tyanboot  
       2020-03-22 02:30:06 +08:00
    @hhhsuan #17 如果不用切换线程的话, 那 launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) 是否符合呢. launch 的时候选择用单线程的调度器, 也就没有线程切换的问题了吧.

    @no1xsyzy #24 另外如果定义可以用类似 yield 的方式来交出控制权的话, 那 kt 同样提供了 suspendCoroutine 和 suspendCancellableCoroutine 的方式来暂停, 并提供一个 Continuation 对象来供恢复. 这就和 Rust 的 Future 设计是差不多的意思.
    araaaa
        29
    araaaa  
       2020-03-22 09:38:28 +08:00 via iPhone
    将函数式代码转为命令式,减少嵌套提高可读性
    no1xsyzy
        30
    no1xsyzy  
       2020-03-23 00:46:55 +08:00
    @Tyanboot 实际上我刚花了半小时看了下 kotlin 语法(
    感觉是 delay 之类的阻塞操作隐式交出控制权这样。隐式是可以的。
    suspendCoroutine 没看太看明白……
    用法大概是
    launch {
    // doSomething
    passed_value = suspendCoroutine( continuation -> some_global_variable = continuation)
    // after continued
    }
    launch {
    // doSomething
    some_global_variable.resume(pass_value)
    // not reachable
    }
    这样?那其实就是做了个 first-class continuation (虽然这个 continuation 是个一次性的,到底算不算 first-class 我也不知道),并且把逃逸路线控制在 launch 上吧…… 怎么说…… 挺原汁原味的……
    Tyanboot
        31
    Tyanboot  
       2020-03-23 18:45:05 +08:00
    @no1xsyzy 大概就是这样的用法, 不过 resume 的时候不需要放在 launch 里面, 毕竟 launch 只是用来启动一个协程的, kotlin 把这玩意叫可暂停的函数罢了。

    delay 这个函数就长这样

    suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
    cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
    }

    里面也是直接调用了 suspendCancellableCoroutine 来暂停的. 这样很多阻塞操作用户看来感觉就像是隐式的, 其实都是函数调用链里面某一层显式的用了 suspend*Coroutine 来暂停的,包括什么 channel 的 receive,mutex 的 lock 之类的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2686 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 07:41 · PVG 15:41 · LAX 23:41 · JFK 02:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.