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

今天有个面试官和我讲 go 的协程比系统的线程更慢,这个我不能理解

  •  1
     
  •   qxdo1234 ·
    qxdo · 1 天前 · 4363 次点击
    我不知道他的回答和我的回答哪个是有依据的,麻烦有大佬知道的,指正我一下,仅是探讨技术对错问题,谢谢。

    他一上来问我 go 的协程能否做到线程不能做到的事,而且至少重复问了我 3 次。我回:总的来说是可以加快程序的运行效率。他就讲出了他的理论和依据,既然 go 协程是要由线程去接管运行的,资源也是从线程分来的,那么何谈加快运行效率,你原本线程要做的事还是没变,而且还多了管理协程的开销。后来他又提了一些问题试图来让我相信他这个理论和依据,不知道其中某个问题的时候,我回的是:不耗费资源的操作时,协程要更快,在耗费资源较多时,还是线程更快。然后他还是在反复和我纠结这个问题。在我看来 go 的协程实现是分割原本线程的资源,做到更轻量化和更灵活的资源调度。调用完资源空闲了就可以及时 gc ,就可以用更少的资源去做更多的事。到最后,他才说,我的大前提是,要做的事是非常耗费资源的操作,就感觉很搞不懂。

    虽然我面试问题回答的很差,但是我依旧想知道这个问题,不知道有没有大佬来和我指正一下,
    另外他还有第二个问题,既然协程这套理论这么牛逼,那么 c++ 为什么没有呢?(在我印象里 c++只有线程)
    51 条回复    2025-03-23 06:06:18 +08:00
    huluhulu
        1
    huluhulu  
       1 天前
    C++有协程啊,谁说没有的。。。
    Donahue
        2
    Donahue  
       1 天前   ❤️ 3
    线程有上下文切换开销,协程没有(或者更小?)
    线程占用内存大,协程占用内存更小,可以使用更多协程

    协程更多是为了异步/减少阻塞吧(不知道对不对哈)
    为了减少阻塞,从 callback function -> async/await -> 协程
    当遇到阻塞的时候,协程可以由协程调度器调度到其他协程,并且上下文切换的开销小。
    如果换作线程,阻塞就浪费 cpu 了。

    c++有协程了
    qxdo1234
        3
    qxdo1234  
    OP
       1 天前
    @huluhulu 哦?是有的吗?我不是 c++开发者,仅有的一点对 c++的认知可以理解成是跑个 hello world 的级别。
    w568w
        4
    w568w  
       1 天前   ❤️ 3
    > 在我看来 go 的协程实现是分割原本线程的资源,做到更轻量化和更灵活的资源调度

    没什么问题。更具体地说,很重要的一个原因是 userspace thread 完全省略了操作系统调度线程和内核态切换的开销。

    有一个类似的例子:为什么 C 语言里用 malloc() 分配内存,而不是直接调操作系统提供的 sbrk()?你让面试官想去吧。

    > 他还有第二个问题,既然协程这套理论这么牛逼,那么 c++ 为什么没有呢

    没更新过知识的愚昧认知。当今几乎所有现代语言里都有「协程」,只是具体含义和实现不同。我随便抓一把:Python 、JS/TS 、C++、Rust 、C#、Java/Kotlin 、Lua 、Dart…
    so1n
        5
    so1n  
       1 天前
    假设你有一件事的情况下,协程的管理消耗时间远远大于执行时间,线程能比协程更快。但是你有很多件事的情况下,协程能省下很多上下文切换的开销,这部分远远大于协程的管理时间
    sardina
        6
    sardina  
       1 天前
    goroutinue 的上下文切换是比线程要轻量的,还有一个 goroutinue 初始化只需要才 2K 的内存,一个线程就要 8M ,随便并发一下都比线程的并发多很多
    strobber16
        7
    strobber16  
       1 天前
    这人技术和处事都有问题,要是入职后是你直接上级或者同组的话,这个岗可以直接 PASS
    kneo
        8
    kneo  
       1 天前 via Android   ❤️ 1
    一个协程不会比一个线程更快,但是一万个协程很可能会比一万个线程更快。因为线程本身就是一种资源。你们讨论的“消耗资源”太含糊了。争执前先定义清楚。
    w568w
        9
    w568w  
       1 天前   ❤️ 10
    @w568w 还有个老生常谈的称呼问题:

    协程 = 有栈协程/虚拟线程/用户态线程。这是在说 Go 的 goroutinue 、Java 的 Virtual Thread ;

    协程 = 无栈协程/暂停之后能恢复的函数。这是在说 Python/Dart 的 Iterator 、Lua/C++20 的 Coroutines 、Rust 的 Future 状态机;

    协程 = 可以指包装了一层 Dispatcher 的普通线程。这是在说 Kotlin 的 Coroutine + NewThreadDispatcher 。
    ly841000
        10
    ly841000  
       1 天前
    协程不是为了提高任何效率, 而是为了将异步编程简化成同步化, 1:N 有栈协程好几十年了, unix 和 windows 都有专门的 api, 不是什么新概念, 无栈协程是最近些年编译器进步兴起的
    qxdo1234
        11
    qxdo1234  
    OP
       1 天前 via Android
    @strobber16 别人直接就把我 pass 了,他是一面,他觉得我回答的技术不太行。
    lance6716
        12
    lance6716  
       1 天前 via Android
    > 那么何谈加快运行效率,你原本线程要做的事还是没变,而且还多了管理协程的开销
    > go 的协程比系统的线程更慢

    老哥你是真分不清这两种表述吗…

    一个 CPU 密集的任务,机器不切换协程要跑 100 秒,管理协程花 1 秒,因此 101 > 100 协程更慢
    NotLongNil
        13
    NotLongNil  
       1 天前
    要搞清楚一个事,每一个问题,都是有其前提条件的。比如,面试官说线程比协程快,如果前提条件是并发数很低的情况(并发数比 CPU 核心数少),那么是对的。你认同的观点,同样也是有前提条件的。讨论一个问题前,要先划好场景,不然就是没完没了,毫无意义的扯皮。这家公司 pass 吧
    cnbatch
        14
    cnbatch  
       1 天前
    C++的协程都已经出了好几年了

    我上星期才发了个帖子,用协程写 Demo 程序: /t/1117106
    sagaxu
        15
    sagaxu  
       1 天前   ❤️ 3
    协程运行在线程内,怎么可能比线程更快?越是 CPU 密集型场景,协程越无用。线程跟 CPU 核心 1:1 绑定,设定好亲和性,才是最快的,多 CPU 时还要考虑 NUMA 尽量访问本地内存。

    协程搞到冒烟,也不可能比手搓的多线程实现更快,只不过多线程实现复杂度较高。
    dearmymy
        16
    dearmymy  
       1 天前
    协程就是,一个线程里调度运行的函数。简单讲,当去做 io 读取操作,其实很多时候是内核在读取,用户态这时候没必要等着内核读取完,把这段时间给其他函数运行,等内核读取完后继续运行。
    最常见场景,爬虫,每个请求中间大量是等待 io 读取网络时间,这时候用协程就好。 还有一个 ui 常见例子,点击读取 button ,去读一个大文件并显示当 list 里,以前为了不卡死 ui 要不异步去读,要开线程,都会回调满天飞。协程就好,ui 线程读取大文件,等待过程还继续处理 ui 事件,然后读取完,继续显示列表,甚至代码逻辑就在一起。

    线程是要有一套自己资源,开一个线程是废资源,其实现在配置无所谓,只不过多线程代码很多问题。但是如果代码都非 io 操作那就只能多开线程,这时候协程就没用了。
    bronyakaka
        17
    bronyakaka  
       1 天前   ❤️ 2
    1 、goroutine 初始栈 2KB (会动态增长的,并不是说一定省内存了),而操作系统线程的栈通常 1MB
    2 、Go 运行时内置调度器,相比线程由操作系统调度,goroutines 的上下文切换成本更低,避免内核态的开销。
    3 、配合通道减少了竞争的问题,使用简单
    缺点也有:
    1 、没法控制怎么分配到 cpu 核上,开几个协程可能都挂一个线程上,,利用不了多核资源
    2 、GUI 生态多是线程绑定,go 对这块的兼容很不好,没什么生态
    3 、协程一旦执行阻塞系统调用,会把整个线程阻塞,导致该线程无法执行其他 goroutines ,降低并发效率
    4 、协程不适合 CPU 密集型任务,因为没什么 io ,上下文切换反而增加了开销,,调度器也有损耗,不如用多线程直接绑定到核心上
    CEBBCAT
        18
    CEBBCAT  
       1 天前   ❤️ 1
    “耗费资源”这个概念在你们交谈里面变得很模糊,欠缺定义。

    楼主经验少点,工作几年了哦?可以刷些 Go 实现、Linux 调度的文章,了解下进程这块相关的知识。
    面试官应该到最后解释一下的。


    Go 的协程就是用户(态)自己管理的代码片段嘛,那资源的分配上相比 OS 提供的线程,肯定是能够自己 DIY 啦
    至于 CPP ,那 CPP 人家是写 OS 的语言,我拿 CPP 写个 Linux 再写个 Golang 编译器,你说我 CPP 有没有协程?

    https://draven.co/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/
    https://samwho.dev/memory-allocation/

    https://www.luozhiyun.com/archives/518
    https://draven.co/golang/docs/part2-foundation/ch04-basic/golang-function-call/
    MrKrabs
        19
    MrKrabs  
       1 天前
    你 CPU 跑满那肯定是系统线程快啊,但是你 CPU 跑满的东西你用 go 写?
    mooyo
        20
    mooyo  
       1 天前
    计算密集型肯定是更慢的,协程主要是方便让一个程序更好的写成“正确的”并发模式。
    fanxinxiaozhan
        21
    fanxinxiaozhan  
       1 天前 via Android   ❤️ 2
    cpu 密集型用线程,io 密集型用协程
    fgwmlhdkkkw
        22
    fgwmlhdkkkw  
       23 小时 43 分钟前 via Android
    @MrKrabs 这种情况在 go 里面也是一样啊,全是计算的时候,go 也没办法打断吧。
    hefish
        23
    hefish  
       23 小时 28 分钟前
    op 哥,你碰到了一个杠精面试。pass 吧。。。
    000sitereg
        24
    000sitereg  
       23 小时 0 分钟前 via Android
    其实也没那么复杂。一般又通俗的理解就是计算机的东西越底层效率越高,协程基于线程就不可能比线程的效率高。
    DIO
        25
    DIO  
       22 小时 56 分钟前
    我面试过一个号称某大厂主任级别的技术管理,结果我们聊关于国内外( b 站,油管等)视频下载技术问题。我不太懂但是市面上有这么多盗版视频,总不能都是内部泄漏的吧,就觉得肯定有办法。他让我回去好好看看,说现在大厂都有办法杜绝任何方式扒源。。。
    testcgd
        26
    testcgd  
       22 小时 48 分钟前 via Android   ❤️ 1
    你们不在一个频道上啊,你应该一顿 gmp 叭啦叭啦上去糊他一脸
    没有啥是协程能做线程不能做的,用户态写个协程库就等态了
    1 、协程是为了提高资源利用率和减少上下文切换的开销
    2 、c++也有协程,只是不是语言层面上的
    kingcanfish
        27
    kingcanfish  
       22 小时 21 分钟前
    @qxdo1234 #3 https://github.com/Tencent/libco 微信的 cpp 协程库 还有 这面试官水平太臭
    Flourite
        28
    Flourite  
       22 小时 3 分钟前
    水货
    1. goroutine 也是要线程来运行的啊,性能只会一样
    2. linux 线程栈空间 8M ,相关上下文切换需要保存的寄存器等资源比 g 更多
    3. 让他看新闻,c 跟 cpp 都有 coroutine
    xjzshttps
        29
    xjzshttps  
       22 小时 2 分钟前
    线程成本高:

    * 线程堆栈占用内存大
    * 线程切换成本高,是需要内核才能切换

    协程成本低:

    * go 的堆栈是动态的,最初只会使用很小的内存空间
    * go 协程切换是用户态的,成本低



    另外 go 适合 io 密集型的,原生线程适合计算密集型。
    zhmouV2
        30
    zhmouV2  
       21 小时 58 分钟前 via Android
    @DIO 这什么大厂主任😂确实挺次的吧
    agagega
        31
    agagega  
       21 小时 54 分钟前 via iPhone
    我总觉得这个面试官是喜欢在网上刷语言不重要,重要的是思想的那类人🤣
    fds
        32
    fds  
       21 小时 19 分钟前
    其实前面不少回答已经很准确了,我就补充下面试官的想法。面试官无非是想看看你对程序运行时的理解是否足够深入。Go 算是 C 语言的加强版,像 GC 、map 、channel 这些,你用 C 写就得找库或者自己实现,但 Go 就给你包装好了。协程也是一样,你用着是协程,但底层还是在线程上跑的,只不过 Go 帮你把调度逻辑写了,一个线程上可以根据需要不断切换执行各个协程的逻辑。你自己也可以实现这个,但太麻烦,而且大多数人写不对。至于为什么必须有线程,那是因为操作系统就只支持到线程。所以面试官说的确实没问题,算是考察下操作系统吧。当然这个知识点我觉得也就是层窗户纸。
    mayli
        33
    mayli  
       21 小时 17 分钟前
    > 不耗费资源的操作时,协程要更快,在耗费资源较多时,还是线程更快。

    有点笼统,资源的定义是啥没说清楚,不过

    > 总的来说是可以加快程序的运行效率

    这一点肯定不对,协程仅仅是增加了程序的并发度,效率不一定高。一般来说,协程和线程都是解决 IO 阻塞时 cpu 空闲问题,协程可以实现更高阻塞并发,线程虽然并发程度没有协程高,但是总体上一般认为效率比协程还是高的。这里的效率指的是,协程还需要额外的开销进行 cooperative 部分,比如把异步、回调包成类似同步的操作。
    换一个说法,就对于非阻塞 IO 密集型任务,比如 CPU 上纯纯的 for-loop ,协程就毫无用处。

    你回答给人感觉认识不够清晰,估计就 pass 了。或者是你跟他不匹配,觉得沟通费劲,至少你自己也觉得跟他沟通费劲,以后做同事也没意思。不如友好 byebye
    liangzaiyi
        34
    liangzaiyi  
       20 小时 29 分钟前
    搞好前提就方便回答了。如果是 CPU 密集型,直接线程数等于 CPU 核数行了;如果是 IO 密集型,就算是单线程跑协程也比多线程好,资源的分配都不是一个级别的,可以搞一百万个协程,你试下搞一百万个线程看炸不炸。
    Cannly
        35
    Cannly  
       20 小时 15 分钟前 via iPhone
    认同 15 楼说法。面试官的问题应该分场景的。
    如果一个线程的任务就是能跑满一个时间片,而不会在自己的时间片中提前结束任务,那么协程完全不必要。但是如果一个线程所分配的任务,比如只需要 1/10 时间片,那么,引入协程改造,确实能把未引入协程时给其它 cpu 的任务,以协程调度方式继续在本线程运行。这就减少了线程切换,更大限度的利用了 cpu 。
    但是别忘了,操作系统调度的是线程作业,程序尽早出让某个线程 CPU 也是一种协调。尽可能霸占未必主流大部分程序
    qxmqh
        36
    qxmqh  
       20 小时 12 分钟前
    太纠结技术细节,从对话能看出来,即使进入岗位,以后你的日子也不好过。
    cowcomic
        37
    cowcomic  
       20 小时 11 分钟前
    如果只讨论协程和线程自身内部运行时的性能,那一定是线程更好
    但线程的代价更高,占用的系统资源更多,创建线程的时间更长,线程间的资源交互更慢
    所以在使用层面是需要通过应用场景来确定具体用哪个
    hwdq0012
        38
    hwdq0012  
       18 小时 42 分钟前
    c++17 之前 Boost fiber(类似 go 的协程) , boost croutine(没有调度器的协程) 都是有栈协程, c++17 开始 msvc 先有无栈协程, cpp 20 开始 各编译器都陆续有无栈协程了
    HaibaraDP
        39
    HaibaraDP  
       18 小时 36 分钟前
    计算π值,我认为线程还是比协程快的,毕竟协程在线程的基础上套了一层。对于面试问题,应该反复和面试官沟通,确认他的问题后给出答案
    LotusChuan
        40
    LotusChuan  
       18 小时 16 分钟前   ❤️ 1
    这不是技术问题,是面试套路问题。面试得看人下菜,这也是体现沟通能力的一环。

    问题 1 他想听到的就是协程适合 IO 密集型的业务,线程适合 CPU 密集型/普通业务。因为搜索引擎去搜网上论坛都是这种回答,面试官看过所以拿来问了,你得和他看的八股保持一致。

    问题 2 他想听到的是协程很复杂,相比于线程来说会增加代码复杂性。他这么问的原理同上,网上论坛都是这么说的。至于 C++到底有没有协程他无所谓。

    如果你有信心和能力说服面试官,那么再去尝试说服,不然就借坡下驴背他想听的八股就行了。一般来说面试官是说服不了的,因为他控制你面试是否通过的权力,为什么要接受你那一套?毕竟他自己那套也不是全错。

    搜索引擎搜 rust async vs thread 的结果,第一条是 Reddit ,第二条是 Stackoverflow ,完全匹配面试官思路。

    https://www.reddit.com/r/rust/comments/jgpvi3/asyncawait_vs_threadsatomics_and_when_you_use_each/

    https://stackoverflow.com/questions/78541829/async-thread-vs-std-thread
    zzhirong
        41
    zzhirong  
       18 小时 14 分钟前
    两者本质上都可以抽象成,一个线程池在完成多个任务队列,那么问题来了,既然两者都差不多,然后,Go 还引入了
    goroutine 抽象层,为什么 Go (可能)要高效一些。如果所有任务都是非阻塞的,那么多线程和 goroutine 在性能表现上差别可能并不明显(猜想,未验证);但在现实情况中,由于 I/O 或通信等原因,不可避免会发生阻塞。传统线程一旦阻塞,则会占用整个线程资源,而 goroutine 在阻塞时会被挂起,并在等待条件满足后重新调度,大部分时候不会需要阻塞底层线程,从而更高效地利用系统资源。也就是说,如果你很 NB ,能够做到又能尽量少阻塞线程,又能把任务完成(也就是高效利用线程池,这就是 Go 调度器做的事情),那么两者差别不会很大。
    leonhao
        42
    leonhao  
       17 小时 42 分钟前
    根据实际业务测一下就知道了,嘴上说有啥用
    hashakei
        43
    hashakei  
       17 小时 34 分钟前


    moudy
        44
    moudy  
       17 小时 32 分钟前 via iPhone
    @sagaxu 多线程很可能要加锁,携程规划好了不用锁
    sagaxu
        45
    sagaxu  
       17 小时 16 分钟前
    @moudy 协程不过是多线程+运行队列调度+当前 task 的上下文保存/恢复,go 是从语言层面做的,kotlin 是从库层面做的,还有一堆人用 C/C++做了类似的事情,没有什么同步方式是协程能用,线程却用不了的。二十多年前,putty 作者写 putty 的时候就用寥寥数行代码实现了上下文的保存和切换。
    cexll
        46
    cexll  
       17 小时 3 分钟前
    刷一下 linux 底层进场线程协程的位置就知道 内核态和用户态 协程在用户态 创建与销毁都在内核态操作的
    iOCZS
        47
    iOCZS  
       16 小时 55 分钟前
    首先切换效率,协程更高。
    其次,为什么要切换? io 的时候,让出 CPU 资源给其他任务运行,提高运行效率。
    线程爆炸的时候,线程会一直切换。协程一般会控制任务队列(线程数),让多个协程在有限个线程内切换。
    如果每个核心都进行 CPU 密集运算,那效率会比进行额外协程切换的高。
    iOCZS
        48
    iOCZS  
       16 小时 55 分钟前
    @moudy 让我想到了 actor 模型
    leetom
        49
    leetom  
       15 小时 25 分钟前
    我觉得不一定是他要让你相信他的理论,而是对你的回答不满意,通过提出一些不一致的看法,让你深入回答问题,看看你的基本功。
    laminux29
        50
    laminux29  
       11 小时 56 分钟前
    简单来说,启动一次线程去干活,相当于从家里出发去公司干活。线程干完一件事情后,还要回家。接到新任务需要从家里再次出发。协程就没这么多事,一直呆在公司,干完一件事情后,不需要回家,继续在公司干别的。这不效率差别就体现出来了。
    moudy
        51
    moudy  
       1 小时 32 分钟前
    @sagaxu thread 和 coroutine 最大的区别就是上下文切换是否自主可控。因为 thread 切换不可控,所以要应付数据一致性的地方比 coroutine 多得多。相关的开销也大不少。我之前帮同事用高级语言模拟一些不依赖底层的同步方案( bare metal ),在模拟时真的是能很明显的体会这两种技术方案在效率上和 deterministic 上的区别。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1135 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 23:38 · PVG 07:38 · LAX 16:38 · JFK 19:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.