• 请不要在回答技术问题时复制粘贴 AI 生成的内容
xmge
V2EX  ›  程序员

golang 面试之协程比线程更轻量级?

  •  
  •   xmge · Jun 4, 2020 · 8684 views
    This topic created in 2347 days ago, the information mentioned may be changed or developed.

    最近在 golang 面试,一般都会设计到为什么 go 语言更好地支持高并发,

    答:在高并发场景下,创建一个协程比创建一个线程消耗的资源少很多,一般线程创建需要 8Mb,而协程只需要 2kb,所以在同性能的服务器下,协程可以支持更多的并发量。

    有的面试官就会问:协程比线程为什么少占这么多资源?

    h 在网上搜索相关文章,并没有找到很直接,很具体的说法,

    是不是只有我一个人不知道啊,望知道的大佬给普及下,感谢....

    42 replies  •  2020-06-06 21:07:07 +08:00
    Austaras
    Austaras
    Jun 4, 2020❤️2
    1
    neoblackcap
    neoblackcap
    Jun 4, 2020
    2
    goroutine 只是在用户态创建一个数据结构,然后给它分配对应的堆。内核里面是没有对应的线程对象创建的,显然就低很多了
    CEBBCAT
    CEBBCAT
    Jun 4, 2020 via Android
    3
    GMP,请。

    这个问题其实是有点点难度的,首先要了解线程开销,然后还要明白 Go 是怎么调度协程的。对于进程上下文、kernel&CPU 工作原理应该都要有了解吧
    CEBBCAT
    CEBBCAT
    Jun 4, 2020 via Android
    4
    @CEBBCAT 我记得加了“其实我也是菜鸡”的,怎么发出来没有了。。。
    sadwin
    sadwin
    Jun 4, 2020❤️1
    5
    一言蔽之:协程不是内核线程,是 runtime 自己支持的调度机制,本质上是单线程
    ke1e
    ke1e
    Jun 4, 2020 via Android
    6
    因为它只是个数据结构
    wellsc
    wellsc
    Jun 4, 2020
    7
    建议买本操作系统的书看看
    freemoon
    freemoon
    Jun 4, 2020
    8
    这个问题就要深入了解 go 的两级线程调度模型了,简单来说 go 是自己的 runtime 中实现了用户线程和系统线程的相互转换调度的过程,其他大部分语言的用户线程基本都是直接 1:1 系统线程,线程调度交给了内核,而 go 的用户线程与内核线程的配比是 M:N,go 在这两种线程之间的转换调度做了不少工作,正式这个做了这个优化才使得 go 的用户线程相比于其他语言的用户线程而言是十分轻量的。
    chihiro2014
    chihiro2014
    Jun 5, 2020
    9
    其实感觉就是个 Java 中的单线程线程池(讲的不对,但感觉就是这么一回事)
    linvon
    linvon
    Jun 5, 2020
    10
    GMP 调度模型, 用户态调度与内核态调度的区别,动态栈增长
    gdt
    gdt
    Jun 5, 2020
    11
    协程上下文切换不需要切换到内核态,上下文切换的代价小。
    gdt
    gdt
    Jun 5, 2020
    12
    为什么协程切换的代价比线程切换低? - 暗淡了乌云的回答 - 知乎
    https://www.zhihu.com/question/308641794/answer/572499202
    gdt
    gdt
    Jun 5, 2020
    13
    为什么协程切换的代价比线程切换低? - 张凯(Kyle Zhang)的回答 - 知乎
    https://www.zhihu.com/question/308641794/answer/570701196
    snxq1995
    snxq1995
    Jun 5, 2020 via Android
    14
    协程是用户态的,线程是内核态的。内存占有小
    协程是 go 运行时调度,线程是系统调度。切换成本小。
    sagaxu
    sagaxu
    Jun 5, 2020 via Android
    15
    一般线程创建需要 8Mb 吗?

    即便笨重如 Java,一个线程也就 1M
    wangyzj
    wangyzj
    Jun 5, 2020
    16
    不是 epoll 吗?
    ppphp
    ppphp
    Jun 5, 2020
    17
    楼上说的很对,M:N 的模型保证了它的性能和足够好用,其实这个实现起来并不容易
    vk42
    vk42
    Jun 5, 2020
    18
    线程创建要 8MB ???进程表示瑟瑟发抖……
    hercule
    hercule
    Jun 5, 2020 via iPhone
    19
    这么多人回答,连问题点都没理解到,别人说的为什么协程比线程资源占用少,不是什么调度机制,什么内核态用户态
    hercule
    hercule
    Jun 5, 2020 via iPhone
    20
    虽然我也不知道答案
    gimp
    gimp
    Jun 5, 2020
    21
    线程运行在进程中,协程运行在线程中。
    ipwx
    ipwx
    Jun 5, 2020
    22
    因为切换线程是内核管的,要存储 /恢复一堆上下文。因为线程是通用的模型,所以操作系统内核为了不出错,会比知道更多程序运行细节的 go 编译器做更多的备份 /还原操作。另外切换线程进内核是需要中断触发的,又是一套比较复杂的流程。
    ipwx
    ipwx
    Jun 5, 2020
    23
    @hercule 肯定是内核态用户态啊。协程是 go 模拟出来的,一堆协程运行在同一个线程上,Go 因为知道更多程序运行信息,不需要做 overkill 的上下文备份 /还原,上下文开销自然就小了。而且不用过中断,不用进内核(进内核需要改 CPU 特权等级,有不少操作要做),省了太多事情。
    129tyc
    129tyc
    Jun 5, 2020 via Android
    24
    答案很简单,因为线程创建会分配更大的调用栈空间,比如 linux 下可以是 10M,而 go 协程创建默认的调用栈大小是 2K
    justicelove
    justicelove
    Jun 5, 2020
    25
    就是内核线程和用户线程的区别, java1.2 之前也是用户线程,后来改成了内核线程
    BingoXuan
    BingoXuan
    Jun 5, 2020
    26
    现实当中,裸机跑程序效率最高的。加入 OS 这么方便的一层后效率就低很多了。所以要高效最好不要让操作系统重新调度资源。因而在操作系统最小调度单位线程下,通过协程做并发处理好过用操作系统线程调度。如果进程是厂房,线程是流水线,那么协程流水线自动适应生产不同产品,避免闲置流水线或新建流水线来生产。让 n 条流水线生产出 m 种产品( m>n )。
    ylsc633
    ylsc633
    Jun 5, 2020
    27
    你这个问题 我去某大厂面试也被问到! 一模一样的! 可惜当时我并没有看过也不记得操作系统相关

    emmmm.. 现在也没怎么看,就看了一些大佬关于 gmp 的!

    然后根据文章 汇总了一篇文章

    http://interview.wzcu.com/Golang/goroutine.html#goroutine-%E5%92%8C-thread-%E7%9A%84%E5%8C%BA%E5%88%AB

    希望对你有帮助(里面还有上百套笔试题,很多都是我面试亲自遇到收集的)! 另外, 我的想法是 还是得深入研究下 操作系统..(毕业多年,已完全忘光)
    ylsc633
    ylsc633
    Jun 5, 2020❤️1
    28
    对了 记得看下这个概念 http://interview.wzcu.com/Golang/morestack.html
    keshawnvan
    keshawnvan
    Jun 5, 2020
    29
    1.堆栈刚开始比较小
    2.比起线程去掉了线程局部存储
    fiypig
    fiypig
    Jun 5, 2020
    30
    @ylsc633 大佬可以的 ,收藏起来了
    Philippa
    Philippa
    Jun 5, 2020
    31
    大家看看第一个回答的视频,一个个技术要求来不断进化设计,看 20 分钟就好了,后面太多细节。就是那貌似是俄语口音的英语也只有自动英语字幕,不容易听。
    mengzhuo
    mengzhuo
    Jun 5, 2020
    32
    1. 线程没这么多,一个 Goroutine 现在是 384 字节
    2. 轻量是因为,线程上下文切换不仅大,还需要 flush TBL
    haha370104
    haha370104
    Jun 5, 2020
    33
    本质上就是单线程,我的个人理解是 runtime 机制带来的程序内的控制权转交
    6ufq0VLZn0DDkL80
    6ufq0VLZn0DDkL80
    Jun 5, 2020
    34
    @mengzhuo 同进程的线程切换为什么要刷 tlb,而且新一些的 cpu 上有 asid 不用把 tlb 全刷掉了。
    yukiloh
    yukiloh
    Jun 5, 2020
    35
    一楼一上来是个空白,吓了我一跳..
    mengzhuo
    mengzhuo
    Jun 5, 2020
    36
    @cholerae 搞错哈,现在不用刷的话性能应该没有什么区别了吧。
    liuxingdeyu
    liuxingdeyu
    Jun 5, 2020
    37
    在想一个问题,是不是还有个原因,就是协程使用的时间成本相对小于线程,因为协程的时间粒度更小
    lff0305
    lff0305
    Jun 5, 2020
    38
    @sagaxu 没这么大, C/C++ call createThread api 的时候可以设置栈大小, 默认好像是 1M
    早几年记得 Java 好像是 512k 默认, 后来改成 128k 了 (可以用 -Xss 设置)
    xsen
    xsen
    Jun 5, 2020❤️1
    39
    1. 协程是轻量级的线程,就是说资源占用少很多

    2. 可以认为协程是用户态的轻量级线程,不会涉及到上下文切换——那自然效率更高、性能更好
    这个类似用户态的网络协议栈,一样的道理

    其实从模型上来说,协程的底层机制类似基于 mq 的任务分发机制;只是协程底层针对一般的 mq 任务分发,多做了一层虚拟资源的调度中心(调度逻辑 cpu 资源)
    xsen
    xsen
    Jun 5, 2020
    40
    也就是把一个线程当成一个逻辑 cpu,然后 go 做了一个基于时间片的调度中心,最小任务是协程
    因为都是在用户态(单个线程),所以资源消耗比线程小,没有上下文切换

    所以性能可以更好
    chanchancl
    chanchancl
    Jun 5, 2020
    41
    Golang 中,创建一个协程,仅仅是在用户态下创建一个 Goroutine 数据结构
    而一般的线程,则要到内核态去创建,这之间就涉及到 CPU 在两个状态之间的转换。

    其次 GMP 调度中,G 的调度始终是由 M 来完成的,M 由依赖于后面实际绑定的 P
    P 一般来说就是原生的线程了。在 M 调度不同的 G 也就是 Go routine 时,
    不需要从用户态切换到内核态,只需要将 Go routine 的上下文保存,并从自身队列或者全局队列寻找需要调度的 G,
    如果由,则进行调度,没有,则在积累一定次数之后,解除与 P 的绑定,并休眠,等待一些 singal 的触发。

    本质上来说就是楼上很多人讲的多核:多线程的 M:N 模型,所以调度效率较高,使用的资源较少
    Jony4Fun
    Jony4Fun
    Jun 6, 2020
    42
    @ylsc633 好强!感觉很给力
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1449 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 80ms · UTC 15:47 · PVG 23:47 · LAX 07:47 · JFK 10:47
    ♥ Do have faith in what you're doing.