Kotlin 的协程实现的意义是什么呢? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
insomnia1232
V2EX    Kotlin

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

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

    看了 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 没法在语言级别做到这样写异步代码
    300
        3
    300  
       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  
    PRO
       2020-03-21 03:11:18 +08:00
    如果这个算伪协程的话, 那真协程应该是什么样子, 或者说具有什么特性才算是真协程呢.
    abcbuzhiming
        6
    abcbuzhiming  
       2020-03-21 08:00:54 +08:00   2
    @Tyanboot 协程的定义是“用户级别的多线程”,而传统的“多线程”是有内核态参与的系统级别实现的多线程,这种实现的线程的建立成本不低,因为内核资源宝贵,并且切换线程的时候有所谓的线程上下文切换开销,用户态内核态切换开销。而协程只在用户态,所以新建协程的成本很低,一台机器上可以建远远比线程上限数量高的多的协程,并且因为只在用户态,协程的切换成本也很低。虽然协程的任务最终还是交给系统线程完成的,但是协程的发明是语言技术进步而对内核技术的一次瘦身,多线程技术有内核态参与这个问题本质上是早期的语言性能不够,为了性能,让内核具备了多线程调度的能力,提供了一种通用的多线程编程模型,且性能不错,但是随着时间语言技术开始进步,慢慢的就有人觉得内核参与的线程成本太高了,而用户态这边的硬件和软件资源突飞猛进,有了超越内核态的可能性,于是就有人希望把多线程再次移动回用户态,这本质是技术发展的一种历史轮回
    PDX
        7
    PDX   div class="badges">   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  
    PRO
       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  
    PRO
       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     Solana     2355 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 15:46 PVG 23:46 LAX 08:46 JFK 11:46
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86