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

Kotlin 的协程是真协程吗?被 b 站博主搞蒙了

  •  
  •   714105382 · 2022-01-25 15:51:28 +08:00 via iPhone · 6680 次点击
    这是一个创建于 793 天前的主题,其中的信息可能已经有所发展或是发生改变。

    能做到类似 golang goroutine 完全不阻塞任何 os thread 进行 io 之类的访问并在得到结果后自动把协程挂载到线程继续执行?

    视频上说不能: [ [码上开学] 到底什么是「非阻塞式」挂起?协程真的比线程更轻量级吗?-哔哩哔哩] https://b23.tv/qIkWUmo

    27 条回复    2022-12-16 12:44:55 +08:00
    zxjunz
        1
    zxjunz  
       2022-01-25 16:05:11 +08:00
    协程简单理解就是用同步式的写法去写异步的代码,防止回调地狱。反编译 kotlin 到 java ,本质上还是一层回调嵌套一层回。因此,并不会比线程轻量级。至于你说的,能否实现 golang 的协程效果,完全是可以的,甚至就是为此而设计的。
    jameslan
        2
    jameslan  
       2022-01-25 16:07:49 +08:00
    coroutines 都差不多,算上 js 的,python 的,等等等等。似乎 C#的最早?但是不叫 coroutines

    其实就是借助编译器,把函数拆成一个一个的 task ,然后用 event/message loop 来处理
    Goooler
        3
    Goooler  
       2022-01-25 16:10:46 +08:00
    Kotlin 协程在 Jvm 上的实现就是线程,其他平台上的实现不清楚。
    wellsc
        4
    wellsc  
       2022-01-25 16:11:08 +08:00
    stackfull coroutines
    Kasumi20
        5
    Kasumi20  
       2022-01-25 16:13:06 +08:00
    @zxjunz Java 能写回调吗?把局部变量的访问全部推到堆上?
    sagaxu
        6
    sagaxu  
       2022-01-25 16:14:33 +08:00 via Android
    不能,kt 协程里不能调用阻塞线程的方法,包括且不仅限于文件 IO ,网络 IO
    zxjunz
        7
    zxjunz  
       2022-01-25 16:17:38 +08:00
    @Kasumi20 #5 匿名内部类罢了,所有局部变量都会通过构造函数传到这个内部类里
    Leviathann
        8
    Leviathann  
       2022-01-25 16:20:02 +08:00
    看最终调用的 io 把
    如果在协程里调用阻塞 io 那协程所在的线程还是会被阻塞
    如果因为计算任务停住还能靠线程池的任务偷取

    go 应该是全链路非阻塞 + 内置调度器,所以可以随便调
    ZSeptember
        9
    ZSeptember  
       2022-01-25 16:42:01 +08:00
    go 的协程也会阻塞系统线程啊

    只是 Go 的所有 IO 操作被封装过而已,不会直接调用系统 io

    和 Kotlin 的效果是一样的,只是 kotlin 的想要不阻塞系统 thread ,需要调用特定 API 而已
    fpure
        10
    fpure  
       2022-01-25 16:52:22 +08:00
    协程就是用户态可重入的函数或过程,有的是手动重入比如生成器,有的是自动重入比如 async/await/绿色线程
    fpure
        11
    fpure  
       2022-01-25 16:56:15 +08:00
    协程的关键就是用户态的可重入,其他有栈无栈、自动重入手动重入都是基于此的概念
    tabris17
        12
    tabris17  
       2022-01-25 17:47:35 +08:00
    你应该先区分协程和异步IO的关系
    wxjggr
        13
    wxjggr  
       2022-01-25 18:43:08 +08:00 via Android
    @zxjunz kotlin 协程底层并不是回调嵌套,而是基于状态机的递归调用。
    xfriday
        14
    xfriday  
       2022-01-25 20:45:30 +08:00
    go 里面的 io 都是被包装过的,阻塞会自动调度走,kotlin 你用那些包装过的 io 也可以,不然会阻塞跑这个 coroutine 的线程,( cgo 调用系统阻塞 io ,调度器就没辙了)
    unco020511
        15
    unco020511  
       2022-01-25 22:02:56 +08:00
    kotlin for jvm 的协程是包装的线程,非操作系统层级的协程
    2i2Re2PLMaDnghL
        16
    2i2Re2PLMaDnghL  
       2022-01-26 10:08:16 +08:00
    与协程( coroutine )相对的概念不是线程( thread )、进程( process ),而是子例程( subroutine )
    最夸张地说,使用 Kafka Celery 之类的工具作为唯一信道可以构造跨进程甚至跨机器的协程。
    至于非阻塞 io ,只是和协程写法非常搭配罢了。但你也可以用 ponylang 那样的纯消息模式。

    @zxjunz kotlin 语言层面定义了一次性续延( one time continuation ),在那基础上构建的协程,我不认为编译到回调链能够正确地实现之,尤其是涉及控制结构(条件 / 循环)的时候。

    @Kasumi20 我记得是很早就能写了(看到写 stream 的都写回调)。不过蕴含一个障碍,不能直接修改外层词法作用域的变量,必须使用容器类型,我认为这符合 #7 所说。
    lasuar
        17
    lasuar  
       2022-01-26 10:23:13 +08:00 via iPhone
    如果是系统调用应该还是会占用线程的
    fpure
        18
    fpure  
       2022-01-26 11:22:13 +08:00
    @unco020511 难道还有操作系统层级的协程?协程本来就是应用自己实现的
    SoloCompany
        19
    SoloCompany  
       2022-01-26 19:57:33 +08:00   ❤️ 1
    操作系统哪来的协成概念, 所有协程的概念都是语言提供的, go 是 runtime 层面直接提供, kotlin 则是一个统一的抽象, 在不同的 runtime 上提供不同的实现, 基本上都是翻译成状态机的实现
    shyling
        20
    shyling  
       2022-01-26 21:58:02 +08:00
    协程是协程,异步 IO 是异步 IO 。。
    unco020511
        21
    unco020511  
       2022-01-27 09:41:24 +08:00
    @fpure # 18 是吗,那可能是我理解错了吧
    kingbill
        22
    kingbill  
       2022-03-11 16:21:18 +08:00
    据我的了解,计算机领域协程没有统一的定义(要说统一可能就是线程再上层的抽象),不同语言中对协程的定义都不太一样,不像线程是依赖操作系统的。真协程……不太理解你说的
    D3EP
        23
    D3EP  
       2022-04-14 16:12:36 +08:00
    协程肯定是跑在线程上的,操作系统就没有协程的概念。
    「非对称协程」大多数都是类似 Kotlin 这种实现,将一个函数体拆成多个 subroutine ,执行异步操作时,通过 continuation 保存上下文状态(包括代码行号、局部变量),并从当前函数退出;当异步操作完成时,会通过 continuation 重新进入该函数,并恢复上下文。
    D3EP
        24
    D3EP  
       2022-05-04 20:00:16 +08:00
    上面说错了,应该是「无栈协程」大多数都是类似 Kotlin 这种实现,将一个函数体拆成多个 subroutine ,执行异步操作时,通过 continuation 保存上下文状态(包括代码行号、局部变量),并从当前函数退出;当异步操作完成时,会通过 continuation 重新进入该函数,并恢复上下文。

    Golang 那种是有栈协程。
    Pantheoon
        25
    Pantheoon  
       2022-09-16 20:39:52 +08:00
    其实就是回调,没那么难理解,举个例子:
    doSth(xxx,new Listener(){
    success(){}
    })
    success 就是写在回调里面的,这种方式一个是比较丑陋,第二个是会有回调地狱的问题,doSth 可以跑在主线程,success 可以跑在子线程里面,后面为了解决丑陋的问题,就有了 await 这个关键字,js 和 c#都有,语法就变成了这样:
    await doSth()
    success()
    看到没有,如果有 await 他的语法是等价于上面那种东西的,doSth 可以跑在主线程,success 跑在了子线程里面,也就是用同步的方式写异步代码,线程发生了切换,但执行的顺序是同步的,本质上是回调
    然后到了 kotlin,首先,kotlin 吹嘘协程是对开发产生了误导,他的本质其实就是类似 await,通过语法把回调屏蔽掉:
    launch{
    doSth()
    success()
    }
    suspend doSth()
    而 suspend 关键字就是类似于 await,async 的 async 一样,其实就是打个标记,告诉编译器,后面可以加回调,当线程执行 success 时,doSth 方法没有被执行,也就是所谓的挂起来了,而添加回调的方式叫做 continuation,也就是反编译后是类似这样的语法
    doSth().continuation(success())
    大概这样子
    Pantheoon
        26
    Pantheoon  
       2022-09-16 20:43:15 +08:00
    如果是通过回调的方式,除了 success 方法,还有一个 error 方法
    doSth(xxx,new Listener(){
    success(){}
    error(){}
    })
    但是 kotlin 里面回调的语法是没有处理 error 的,所以它又搞了一堆异常机制,异常在协程里咋传播的,然后咋处理,设计的还挺复杂
    ExplodingFKL
        27
    ExplodingFKL  
       2022-12-16 12:44:55 +08:00
    “轻量”只是相对的概念,对于 CPU 密集的任务改用协程实现并不会有显著的性能提升,协程的主要目的是尽量避免无意义的等待 .
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1137 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:36 · PVG 02:36 · LAX 11:36 · JFK 14:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.