V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
lhx2008
V2EX  ›  Go 编程语言

Go routines 和 Java 线程池的区别有哪些?

  •  
  •   lhx2008 ·
    xenv · 2019-11-01 08:26:39 +08:00 via Android · 6419 次点击
    这是一个创建于 1609 天前的主题,其中的信息可能已经有所发展或是发生改变。
    比如使用上,原理设计上
    33 条回复    2019-11-02 10:57:08 +08:00
    yuikns
        1
    yuikns  
       2019-11-01 08:29:32 +08:00 via Android   ❤️ 1
    go routine 是官方提供的,线程加协程池
    optional
        2
    optional  
       2019-11-01 08:35:53 +08:00 via iPhone   ❤️ 2
    用户态 vs 内核调度
    Orenoid
        3
    Orenoid  
       2019-11-01 08:39:44 +08:00 via Android   ❤️ 1
    Go 不只是线程,还包括协程,具体调度原理就不清楚了
    111111111111
        4
    111111111111  
       2019-11-01 08:41:45 +08:00 via Android   ❤️ 2
    1.协程的成本开销比线程小
    2.协程是用户调度,线程是交给系统调度


    使用上协程应该比线程要多操点心,但 Go 自己有调度器协程使用上其实不怎么操心

    其他的,可能就是省了创建线程管理线程的代码,只写一个关键字就行了
    lhx2008
        5
    lhx2008  
    OP
       2019-11-01 08:45:21 +08:00 via Android
    @optional
    @111111111111
    go 的用户态调度,是不是可以抽象理解为把阻塞代码前后进行分割,分成小的代码块,放入线程池中执行呢
    scukmh
        6
    scukmh  
       2019-11-01 08:55:36 +08:00   ❤️ 2
    @lhx2008 没那么简单,但一般情况下如此。
    baiyi
        7
    baiyi  
       2019-11-01 09:05:53 +08:00   ❤️ 1
    不清楚 java 的线程池,没办法比较

    如果想了解 goroutine,推荐一篇文章: https://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/

    如果想从源码的角度来了解实现,可以看下《 Go 语言学习笔记》的源码剖析,虽然是基于 1.5 版本,但是调度器方面在以后的的版本中更新不多,所以完全没问题
    mikulch
        8
    mikulch  
       2019-11-01 09:27:52 +08:00
    go 语言的包管理器现在完善的如何?
    ahsjs
        9
    ahsjs  
       2019-11-01 09:44:18 +08:00   ❤️ 1
    @mikulch 基本都 go mod 了。
    xmge
        10
    xmge  
       2019-11-01 09:54:51 +08:00   ❤️ 1
    go 的协成是一种轻量级的线程,一个线程通过 go 的调度动态地与多个协程绑定。
    lolizeppelin
        11
    lolizeppelin  
       2019-11-01 10:07:07 +08:00   ❤️ 8
    线程
    一个线程跑满,就吃饱一个 cpu
    N 个线程跑满对应 N 个 cpu,只要与 cpu 数量相同多线程就能吃饱 cpu
    但是实际代码里会阻塞,一阻塞会自动让出 cpu, 所以在阻塞写法里,程序的线程超过 cpu 数量能提高性能
    但是过多的线程数带来的上下文切换回拖慢整体


    协程
    通过寄存器保存代码片段,遇到阻塞(一般是 io,或者自定义的协程锁),会切换到其他代码片段
    所有代码片段的入口存放一个不停排序的队列
    一个循环(相当于一个线程)不停的排序这个队列,并弹出最前面的数据获取到代码片段并执行
    一般只有一个主线程,由于代码片段的切换由程序自己决定,没有系统级上下文切换,性能好,缺点是单线程


    所以性能最好的方式是结合多线程与协程,但是非语言级很难用一个库来实现支持多线程的协程

    目前除了 go, dart (也就是 google 的 flutter 所用语言)也有多线程协程支持


    我瞎鸡巴说的...我没写过 go 哈哈哈哈哈哈
    youxiachai
        12
    youxiachai  
       2019-11-01 10:11:10 +08:00   ❤️ 1
    有兴趣的其实去可以了解一下
    CSP ( Communicating Sequential Process )理论
    richzhu
        13
    richzhu  
       2019-11-01 10:30:30 +08:00
    @lolizeppelin 卧槽 大佬,你这段话我要加入到面试复习笔记里
    ZSeptember
        14
    ZSeptember  
       2019-11-01 11:00:44 +08:00   ❤️ 2
    goroutine 的底层也是线程池,G 相当于一个 Runable。
    goroutine 的优化是将阻塞都放在用户态,自己调度,就不会创建很多的系统线程了。

    比如,将所有的 IO 操作都封装了,底层使用 epoll 之类的非阻塞接口,对外暴露同步阻塞接口,所以不会真正阻塞底层线程。
    同步,锁之类的都是在用户态
    CosimoZi
        15
    CosimoZi  
       2019-11-01 11:35:54 +08:00 via Android   ❤️ 2
    协程的本质就是 continuation
    godoway
        16
    godoway  
       2019-11-01 12:37:17 +08:00 via Android   ❤️ 1
    不如问一下,go 的协程和 kt 的协程有什么区别。
    个人认为 kt 的用起来更简单
    zunceng
        17
    zunceng  
       2019-11-01 13:37:29 +08:00   ❤️ 1
    大部分的 thread pool 都不用实现了 , 在一定程度上协程随便开。

    Ps:一定程度上 ,我没试过开上万个 goroutine, 超过一定程度我估计也会吃不消
    reus
        18
    reus  
       2019-11-01 13:54:26 +08:00
    语义上没有区别。
    lolizeppelin
        19
    lolizeppelin  
       2019-11-01 14:41:24 +08:00   ❤️ 1
    @richzhu

    记这个不如好好把一个协程库的代码读透,比如 python 的 eventlet
    读透了搞清楚模型模型了就自然理解了....啥语言都一个样,记下来真没用....
    lolizeppelin
        20
    lolizeppelin  
       2019-11-01 14:43:07 +08:00
    话说...好像这些都是从 erlang 里出来的?
    JohnSmith
        21
    JohnSmith  
       2019-11-01 16:13:13 +08:00
    协程 vs 线程
    optional
        22
    optional  
       2019-11-01 18:19:21 +08:00   ❤️ 2
    @lhx2008 『用户态调度,是不是可以抽象理解为把阻塞代码前后进行分割,分成小的代码块,放入线程池中执行呢』
    原理上是这样没错,但是有个重要的区别:
    协程:主动放弃 cpu
    线程:抢占式调度
    所以,对于 io 型应用来说,前者少了很多后者『抢到了 cpu,发现数据还没准备好,又放弃 cpu 』的消耗。
    Les1ie
        23
    Les1ie  
       2019-11-01 18:26:58 +08:00   ❤️ 1
    楼上的一个线程跑满吃掉一个 CPU,多个线程多个 CPU 似乎是没分清线程和进程的区别
    如果我没理解错的话,进程一次只能在一个 CPU 上,进程可以调度到其他 CPU 上,但是一次只会在 1 个地方 而线程,是运行在进程的上下文中的。
    lhx2008
        24
    lhx2008  
    OP
       2019-11-01 19:19:13 +08:00
    @optional #22
    抢占式调度怎么理解呢,通常来说,支持抢占式不是更好吗? Go 不是也有说要支持抢占式调度的协程。

    您说的 『抢到了 cpu,发现数据还没准备好,又放弃 cpu 』 是在有锁的情况下才会争抢吧?而且像 Java 的 AQS,抢不到锁也是直接排队等待解锁了。争抢的情况似乎也不是很明显。

    我的理解只是这样调度的粒度更小,所以效率提高了。
    lhx2008
        25
    lhx2008  
    OP
       2019-11-01 19:22:38 +08:00
    @optional #22
    如果是说线程和 CPU 数量相同,可以减少线程之间被 CPU 核切换。那其实线程池也是可以配置的。
    vkhsyj
        26
    vkhsyj  
       2019-11-01 20:28:28 +08:00   ❤️ 1
    线程池还是靠 OS 来调度线程,go 的携程是语言内置的调度器来调度,当然还是受操作系统调度(
    optional
        27
    optional  
       2019-11-01 20:31:11 +08:00   ❤️ 2
    @Les1ie 『线程是 cpu 调度的基本单位』。
    @lhx2008 协程理论上不需要抢占式调度,而且理论上协程不可中断(没有 handle 指向它),除非你把他的执行线程干掉,至于为什么搞出个抢占式的,我只能这么理解『为了防止某个协程一直不主动释放,导致其它饿死』,实现也是怪怪的,做的太多就变成"用户态线程了"。
    至于为什么说协程调度效率高:对于线程调度来说,就是给自己打个 runnable 的标记,然后等调度器赏光,但是对于调度器大爷来说,它不理解你的任务细节,每次给你 300 个时间单位,你可能 10 个单位时间就干完了,剩下的 290 只能还回去(对于 IO 密集型应用这很常见),但是对于协程来说,我干完了可以把这机会给兄弟们用,直到用完这 300 个单位。
    线程池没有调用栈,做不到协程的效果。
    lhx2008
        28
    lhx2008  
    OP
       2019-11-01 20:40:36 +08:00
    @optional #27 谢谢,你讲的微观细节很透彻,我从来没考虑过,不过真的有这么多时间片被浪费了吗
    secondwtq
        29
    secondwtq  
       2019-11-01 21:13:48 +08:00   ❤️ 1
    @lhx2008 "粒度更小"一般对应的是”更加灵活“与”效率更低“
    当然不是说一刀切效率就一定更高,一刀切效果好的前提是切的地方准确

    协作式多任务就正好满足这一条件,进程在明确自己不需要 CPU 时放弃 CPU,而在做事情时操作系统不来烦你,实际就保证了 CPU 一直都在做有用的事情
    可以类比在你写代码时没事总是来催你的产品,没事总是来找你”支持“的队友

    缺点是如果你完成了任务一直不汇报,那整个项目就 block 在你这了
    我认为现代编程语言(好吧 ... 强行把 Go 称为现代编程语言也过得去)对这个问题提供了很好的抽象,所以现在才有这么多人觉得这么好用。这在 Windows 3.1 和 MacOS Classic 那个年代大概是难以想象的吧
    qiyuey
        30
    qiyuey  
       2019-11-01 21:44:51 +08:00   ❤️ 1
    halo117
        31
    halo117  
       2019-11-02 02:50:13 +08:00 via iPhone   ❤️ 1
    @optional 如果只是协作式确实有阻塞其他协程饥饿问题,但 goroutine 调度目前是有 timeout 检测一旦阻塞过久会尝试主动让出执行权,只是让出时机是在特定情况下,并不全面。所以现在看 goroutine 有点半(伪)抢占的意思。后面版本据说会更进一步做成抢占式,不过这样竞态问题会更频繁?
    lhx2008
        32
    lhx2008  
    OP
       2019-11-02 08:15:32 +08:00 via Android
    @halo117 现在的意思好像是,死循环,长时间阻塞到内核态这种情况,就会把执行这个协程的线程扔出调度器?相对于是自动分开一个快的调度线程池和一个慢速线程池。
    reus
        33
    reus  
       2019-11-02 10:57:08 +08:00 via Android   ❤️ 1
    1.14 版本将会加入基于信号的抢占调度,所以 goroutine 从来就不是协程,它的定义就是并发执行的单元,只不过早期的实现是协作式的。在加入函数入口处的抢占检查之后就已经是半协作半抢占调度了,现在要加入完备的抢占调度了。goroutine 的语义也一直和线程接近而不是协程。什么时候调度,程序员没法判断,并发访问内存也需要上锁,就是当作线程来用的,就是一种用户态线程的实现。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   948 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 21:51 · PVG 05:51 · LAX 14:51 · JFK 17:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.