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 天前 · 4194 次点击
    我不知道他的回答和我的回答哪个是有依据的,麻烦有大佬知道的,指正我一下,仅是探讨技术对错问题,谢谢。

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

    虽然我面试问题回答的很差,但是我依旧想知道这个问题,不知道有没有大佬来和我指正一下,
    另外他还有第二个问题,既然协程这套理论这么牛逼,那么 c++ 为什么没有呢?(在我印象里 c++只有线程)
    50 条回复    2025-03-22 19:42:10 +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 天前   ❤️ 2
    > 在我看来 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  
       23 小时 56 分钟前
    C++的协程都已经出了好几年了

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

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

    线程是要有一套自己资源,开一个线程是废资源,其实现在配置无所谓,只不过多线程代码很多问题。但是如果代码都非 io 操作那就只能多开线程,这时候协程就没用了。
    bronyakaka
        17
    bronyakaka  
       23 小时 20 分钟前   ❤️ 2
    1 、goroutine 初始栈 2KB (会动态增长的,并不是说一定省内存了),而操作系统线程的栈通常 1MB
    2 、Go 运行时内置调度器,相比线程由操作系统调度,goroutines 的上下文切换成本更低,避免内核态的开销。
    3 、配合通道减少了竞争的问题,使用简单
    缺点也有:
    1 、没法控制怎么分配到 cpu 核上,开几个协程可能都挂一个线程上,,利用不了多核资源
    2 、GUI 生态多是线程绑定,go 对这块的兼容很不好,没什么生态
    3 、协程一旦执行阻塞系统调用,会把整个线程阻塞,导致该线程无法执行其他 goroutines ,降低并发效率
    4 、协程不适合 CPU 密集型任务,因为没什么 io ,上下文切换反而增加了开销,,调度器也有损耗,不如用多线程直接绑定到核心上
    CEBBCAT
        18
    CEBBCAT  
       23 小时 15 分钟前   ❤️ 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  
       20 小时 35 分钟前
    你 CPU 跑满那肯定是系统线程快啊,但是你 CPU 跑满的东西你用 go 写?
    mooyo
        20
    mooyo  
       20 小时 27 分钟前
    计算密集型肯定是更慢的,协程主要是方便让一个程序更好的写成“正确的”并发模式。
    fanxinxiaozhan
        21
    fanxinxiaozhan  
       18 小时 12 分钟前 via Android   ❤️ 2
    cpu 密集型用线程,io 密集型用协程
    fgwmlhdkkkw
        22
    fgwmlhdkkkw  
       16 小时 15 分钟前 via Android
    @MrKrabs 这种情况在 go 里面也是一样啊,全是计算的时候,go 也没办法打断吧。
    hefish
        23
    hefish  
       16 小时 0 分钟前
    op 哥,你碰到了一个杠精面试。pass 吧。。。
    000sitereg
        24
    000sitereg  
       15 小时 33 分钟前 via Android
    其实也没那么复杂。一般又通俗的理解就是计算机的东西越底层效率越高,协程基于线程就不可能比线程的效率高。
    DIO
        25
    DIO  
       15 小时 28 分钟前
    我面试过一个号称某大厂主任级别的技术管理,结果我们聊关于国内外( b 站,油管等)视频下载技术问题。我不太懂但是市面上有这么多盗版视频,总不能都是内部泄漏的吧,就觉得肯定有办法。他让我回去好好看看,说现在大厂都有办法杜绝任何方式扒源。。。
    testcgd
        26
    testcgd  
       15 小时 20 分钟前 via Android   ❤️ 1
    你们不在一个频道上啊,你应该一顿 gmp 叭啦叭啦上去糊他一脸
    没有啥是协程能做线程不能做的,用户态写个协程库就等态了
    1 、协程是为了提高资源利用率和减少上下文切换的开销
    2 、c++也有协程,只是不是语言层面上的
    kingcanfish
        27
    kingcanfish  
       14 小时 53 分钟前
    @qxdo1234 #3 https://github.com/Tencent/libco 微信的 cpp 协程库 还有 这面试官水平太臭
    Flourite
        28
    Flourite  
       14 小时 35 分钟前
    水货
    1. goroutine 也是要线程来运行的啊,性能只会一样
    2. linux 线程栈空间 8M ,相关上下文切换需要保存的寄存器等资源比 g 更多
    3. 让他看新闻,c 跟 cpp 都有 coroutine
    xjzshttps
        29
    xjzshttps  
       14 小时 34 分钟前
    线程成本高:

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

    协程成本低:

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



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

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

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

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

    你回答给人感觉认识不够清晰,估计就 pass 了。或者是你跟他不匹配,觉得沟通费劲,至少你自己也觉得跟他沟通费劲,以后做同事也没意思。不如友好 byebye
    liangzaiyi
        34
    liangzaiyi  
       13 小时 2 分钟前
    搞好前提就方便回答了。如果是 CPU 密集型,直接线程数等于 CPU 核数行了;如果是 IO 密集型,就算是单线程跑协程也比多线程好,资源的分配都不是一个级别的,可以搞一百万个协程,你试下搞一百万个线程看炸不炸。
    Cannly
        35
    Cannly  
       12 小时 48 分钟前 via iPhone
    认同 15 楼说法。面试官的问题应该分场景的。
    如果一个线程的任务就是能跑满一个时间片,而不会在自己的时间片中提前结束任务,那么协程完全不必要。但是如果一个线程所分配的任务,比如只需要 1/10 时间片,那么,引入协程改造,确实能把未引入协程时给其它 cpu 的任务,以协程调度方式继续在本线程运行。这就减少了线程切换,更大限度的利用了 cpu 。
    但是别忘了,操作系统调度的是线程作业,程序尽早出让某个线程 CPU 也是一种协调。尽可能霸占未必主流大部分程序
    qxmqh
        36
    qxmqh  
       12 小时 44 分钟前
    太纠结技术细节,从对话能看出来,即使进入岗位,以后你的日子也不好过。
    cowcomic
        37
    cowcomic  
       12 小时 44 分钟前
    如果只讨论协程和线程自身内部运行时的性能,那一定是线程更好
    但线程的代价更高,占用的系统资源更多,创建线程的时间更长,线程间的资源交互更慢
    所以在使用层面是需要通过应用场景来确定具体用哪个
    hwdq0012
        38
    hwdq0012  
       11 小时 15 分钟前
    c++17 之前 Boost fiber(类似 go 的协程) , boost croutine(没有调度器的协程) 都是有栈协程, c++17 开始 msvc 先有无栈协程, cpp 20 开始 各编译器都陆续有无栈协程了
    HaibaraDP
        39
    HaibaraDP  
       11 小时 9 分钟前
    计算π值,我认为线程还是比协程快的,毕竟协程在线程的基础上套了一层。对于面试问题,应该反复和面试官沟通,确认他的问题后给出答案
    LotusChuan
        40
    LotusChuan  
       10 小时 49 分钟前   ❤️ 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  
       10 小时 46 分钟前
    两者本质上都可以抽象成,一个线程池在完成多个任务队列,那么问题来了,既然两者都差不多,然后,Go 还引入了
    goroutine 抽象层,为什么 Go (可能)要高效一些。如果所有任务都是非阻塞的,那么多线程和 goroutine 在性能表现上差别可能并不明显(猜想,未验证);但在现实情况中,由于 I/O 或通信等原因,不可避免会发生阻塞。传统线程一旦阻塞,则会占用整个线程资源,而 goroutine 在阻塞时会被挂起,并在等待条件满足后重新调度,大部分时候不会需要阻塞底层线程,从而更高效地利用系统资源。也就是说,如果你很 NB ,能够做到又能尽量少阻塞线程,又能把任务完成(也就是高效利用线程池,这就是 Go 调度器做的事情),那么两者差别不会很大。
    leonhao
        42
    leonhao  
       10 小时 14 分钟前
    根据实际业务测一下就知道了,嘴上说有啥用
    hashakei
        43
    hashakei  
       10 小时 6 分钟前


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