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

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

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

虽然我面试问题回答的很差,但是我依旧想知道这个问题,不知道有没有大佬来和我指正一下,
另外他还有第二个问题,既然协程这套理论这么牛逼,那么 c++ 为什么没有呢?(在我印象里 c++只有线程)
9276 次点击
所在节点    Go 编程语言
76 条回复
zzhirong
191 天前
两者本质上都可以抽象成,一个线程池在完成多个任务队列,那么问题来了,既然两者都差不多,然后,Go 还引入了
goroutine 抽象层,为什么 Go (可能)要高效一些。如果所有任务都是非阻塞的,那么多线程和 goroutine 在性能表现上差别可能并不明显(猜想,未验证);但在现实情况中,由于 I/O 或通信等原因,不可避免会发生阻塞。传统线程一旦阻塞,则会占用整个线程资源,而 goroutine 在阻塞时会被挂起,并在等待条件满足后重新调度,大部分时候不会需要阻塞底层线程,从而更高效地利用系统资源。也就是说,如果你很 NB ,能够做到又能尽量少阻塞线程,又能把任务完成(也就是高效利用线程池,这就是 Go 调度器做的事情),那么两者差别不会很大。
leonhao
191 天前
根据实际业务测一下就知道了,嘴上说有啥用
hashakei
191 天前


moudy
191 天前
@sagaxu 多线程很可能要加锁,携程规划好了不用锁
sagaxu
191 天前
@moudy 协程不过是多线程+运行队列调度+当前 task 的上下文保存/恢复,go 是从语言层面做的,kotlin 是从库层面做的,还有一堆人用 C/C++做了类似的事情,没有什么同步方式是协程能用,线程却用不了的。二十多年前,putty 作者写 putty 的时候就用寥寥数行代码实现了上下文的保存和切换。
cexll
191 天前
刷一下 linux 底层进场线程协程的位置就知道 内核态和用户态 协程在用户态 创建与销毁都在内核态操作的
iOCZS
191 天前
首先切换效率,协程更高。
其次,为什么要切换? io 的时候,让出 CPU 资源给其他任务运行,提高运行效率。
线程爆炸的时候,线程会一直切换。协程一般会控制任务队列(线程数),让多个协程在有限个线程内切换。
如果每个核心都进行 CPU 密集运算,那效率会比进行额外协程切换的高。
iOCZS
191 天前
@moudy 让我想到了 actor 模型
leetom
191 天前
我觉得不一定是他要让你相信他的理论,而是对你的回答不满意,通过提出一些不一致的看法,让你深入回答问题,看看你的基本功。
laminux29
190 天前
简单来说,启动一次线程去干活,相当于从家里出发去公司干活。线程干完一件事情后,还要回家。接到新任务需要从家里再次出发。协程就没这么多事,一直呆在公司,干完一件事情后,不需要回家,继续在公司干别的。这不效率差别就体现出来了。
moudy
190 天前
@sagaxu thread 和 coroutine 最大的区别就是上下文切换是否自主可控。因为 thread 切换不可控,所以要应付数据一致性的地方比 coroutine 多得多。相关的开销也大不少。我之前帮同事用高级语言模拟一些不依赖底层的同步方案( bare metal ),在模拟时真的是能很明显的体会这两种技术方案在效率上和 deterministic 上的区别。
sagaxu
190 天前
@moudy 怎么不可控了?是完全要自己控制,不受外部干预。多线程+task 队列,切换只不过是从队列的 A 元素切换到 B 元素,不是从 A 线程切换到 B 线程。跟 goroutine 的大区别只有一个,多线程+队列是非强占式的,task 不主动释放就会一直占用,goroutine 调度可以抢占。netty 和 vertx 就是多线程+队列,性能差吗?
liuguang
189 天前
go 语言协程的主要用途是哪里,是 web 开发。
web 后端最常见的任务就是等 io ,比起计算任务 io 操作是非常耗时间,而线程遇到 io 操作会卡住,完全不能做任何事,而协程这时候就可以挂起 io 去处理其它事情,所以看上去协程的效率更高。不过实际上协程本质上也是线程,甚至是多个线程合力都有可能,这句话不假,你可以把协程看成一种精心设计好的线程,但是它能在遇到 io 阻塞时,继续去做其它事情。

但是协程就一定比普通线程更快吗,其实并不是的,rust 的异步里面也说了异步操作不一定更快,所以 rust 语言不仅有异步语法,也有原生线程,从而可以看情况来选择。与此相比 go 语言只提供了协程来处理并发,在需要原生线程的地方就会效率更慢。
你可能会问,到底什么时候协程会比线程慢,那就是非 io 密集型的任务,比如说只需要进行密集计算的任务。这种以计算为主要任务的程序,io 操作很少,io 操作当然就不会是线程的瓶颈了。起几个原生的线程去并发运算,自然比起几个协程的效率高,协程总是需要 CPU 去调度切换的,然而这种操作却是毫无价值。
webcape233
189 天前
你说的这个资源硬还是计算密集型( cpu 密集)吧,如果一个线程对比一个协程,cpu 密集型下我想确实没有额外开销的更快
visper
189 天前
当你有一百万个线程和一百万个协程的时候。
OC0311
189 天前
“总的来说是可以加快程序的运行效率” 你可能是知道原理的但是,这个回答确实是没回答到点上。感觉是想考察你对 goroutine 调度的理解。
zxjxzj9
189 天前
理论上来说确实是的,因为携程就是在调度线程,既然是调度肯定有调度开销,最后理论上肯定是比原生慢。 但是实际上除非你是真的需要把 cpu 吃的超级满,否则对 web 端各种 1 核 docker 来说这俩是一模一样的
thevita
189 天前
虽然我也问过类似的问题,但就冲说出 “非常耗费资源的操作”, 这面试官水平就不怎么样,什么叫消耗资源,啥资源啊
duoduoxu
189 天前
la2la
189 天前
在 Linux 系统中线程的切换是需要消耗资源的,那么最节省资源的线程调度算法就是
让线程在每个时间片之内都跑满这个时间不发生阻塞
在程序实际运行中这个算法是不可能实现的,总会有线程需要等到资源比如:IO
那么需要有一个比线程更小的调度单位就是协程
协程的本质就是一个代码块,类似于 todo 操作,在需要阻塞时,todo 到其他协程的开头代码运行,保证在线程时间片内不发生阻塞。
所以评价协程的收益需要看这个 Job 具体执行的任务,如果是纯计算的任务,那么最有效率的是独占 CPU ,其次是进程,线程。
在需要等待资源的 Job ,协程才有效率

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1120244

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX