golang 面试之协程比线程更轻量级? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
xmge
V2EX    程序员

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

  •  
  •   xmge 2020-06-04 20:21:15 +08:00 7960 次点击
    这是一个创建于 2024 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

    42 条回复    2020-06-06 21:07:07 +08:00
    Austaras
        1
    Austaras  
       2020-06-04 20:58:55 +08:00   2
    neoblackcap
        2
    neoblackcap  
       2020-06-04 22:05:01 +08:00
    goroutine 只是在用户态创建一个数据结构,然后给它分配对应的堆。内核里面是没有对应的线程对象创建的,显然就低很多了
    CEBBCAT
        3
    CEBBCAT  
       2020-06-04 23:15:02 +08:00 via Android
    GMP,请。

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

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

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

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

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

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

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

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

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

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

    本质上来说就是楼上很多人讲的多核:多线程的 M:N 模型,所以调度效率较高,使用的资源较少
    Jony4Fun
        42
    Jony4Fun  
       2020-06-06 21:07:07 +08:00
    @ylsc633 好强!感觉很给力
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2759 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 43ms UTC 07:19 PVG 15:19 LAX 23:19 JFK 02:19
    Do have faith in what you're doing.
    ubao msn 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