最近在 golang 面试,一般都会设计到为什么 go 语言更好地支持高并发,
答:在高并发场景下,创建一个协程比创建一个线程消耗的资源少很多,一般线程创建需要 8Mb,而协程只需要 2kb,所以在同性能的服务器下,协程可以支持更多的并发量。
有的面试官就会问:协程比线程为什么少占这么多资源?
h 在网上搜索相关文章,并没有找到很直接,很具体的说法,
是不是只有我一个人不知道啊,望知道的大佬给普及下,感谢....
1
Austaras 2020-06-04 20:58:55 +08:00 2
|
2
neoblackcap 2020-06-04 22:05:01 +08:00
goroutine 只是在用户态创建一个数据结构,然后给它分配对应的堆。内核里面是没有对应的线程对象创建的,显然就低很多了
|
3
CEBBCAT 2020-06-04 23:15:02 +08:00 via Android
GMP,请。
这个问题其实是有点点难度的,首先要了解线程开销,然后还要明白 Go 是怎么调度协程的。对于进程上下文、kernel&CPU 工作原理应该都要有了解吧 |
5
sadwin 2020-06-04 23:31:05 +08:00 1
一言蔽之:协程不是内核线程,是 runtime 自己支持的调度机制,本质上是单线程
|
6
ke1e 2020-06-04 23:32:28 +08:00 via Android
因为它只是个数据结构
|
7
wellsc 2020-06-04 23:38:41 +08:00
建议买本操作系统的书看看
|
8
lasuar 2020-06-04 23:51:45 +08:00
这个问题就要深入了解 go 的两级线程调度模型了,简单来说 go 是自己的 runtime 中实现了用户线程和系统线程的相互转换调度的过程,其他大部分语言的用户线程基本都是直接 1:1 系统线程,线程调度交给了内核,而 go 的用户线程与内核线程的配比是 M:N,go 在这两种线程之间的转换调度做了不少工作,正式这个做了这个优化才使得 go 的用户线程相比于其他语言的用户线程而言是十分轻量的。
|
9
chihiro2014 2020-06-05 00:07:03 +08:00
其实感觉就是个 Java 中的单线程线程池(讲的不对,但感觉就是这么一回事)
|
10
linvon 2020-06-05 00:09:50 +08:00
GMP 调度模型, 用户态调度与内核态调度的区别,动态栈增长
|
11
gdt 2020-06-05 00:18:43 +08:00
协程上下文切换不需要切换到内核态,上下文切换的代价小。
|
12
gdt 2020-06-05 00:20:07 +08:00
为什么协程切换的代价比线程切换低? - 暗淡了乌云的回答 - 知乎
https://www.zhihu.com/question/308641794/answer/572499202 |
13
gdt 2020-06-05 00:23:04 +08:00
为什么协程切换的代价比线程切换低? - 张凯(Kyle Zhang)的回答 - 知乎
https://www.zhihu.com/question/308641794/answer/570701196 |
14
snxq1995 2020-06-05 00:41:48 +08:00 via Android
协程是用户态的,线程是内核态的。内存占有小
协程是 go 运行时调度,线程是系统调度。切换成本小。 |
15
sagaxu 2020-06-05 00:51:08 +08:00 via Android
一般线程创建需要 8Mb 吗?
即便笨重如 Java,一个线程也就 1M |
16
wangyzj 2020-06-05 01:16:43 +08:00
不是 epoll 吗?
|
17
ppphp 2020-06-05 01:51:27 +08:00
楼上说的很对,M:N 的模型保证了它的性能和足够好用,其实这个实现起来并不容易
|
18
vk42 2020-06-05 05:30:48 +08:00
线程创建要 8MB ???进程表示瑟瑟发抖……
|
19
hercule 2020-06-05 07:39:56 +08:00 via iPhone
这么多人回答,连问题点都没理解到,别人说的为什么协程比线程资源占用少,不是什么调度机制,什么内核态用户态
|
20
hercule 2020-06-05 07:40:12 +08:00 via iPhone
虽然我也不知道答案
|
21
gimp 2020-06-05 09:03:33 +08:00
线程运行在进程中,协程运行在线程中。
|
22
ipwx 2020-06-05 09:09:18 +08:00
因为切换线程是内核管的,要存储 /恢复一堆上下文。因为线程是通用的模型,所以操作系统内核为了不出错,会比知道更多程序运行细节的 go 编译器做更多的备份 /还原操作。另外切换线程进内核是需要中断触发的,又是一套比较复杂的流程。
|
23
ipwx 2020-06-05 09:11:07 +08:00
@hercule 肯定是内核态用户态啊。协程是 go 模拟出来的,一堆协程运行在同一个线程上,Go 因为知道更多程序运行信息,不需要做 overkill 的上下文备份 /还原,上下文开销自然就小了。而且不用过中断,不用进内核(进内核需要改 CPU 特权等级,有不少操作要做),省了太多事情。
|
24
129tyc 2020-06-05 09:17:03 +08:00 via Android
答案很简单,因为线程创建会分配更大的调用栈空间,比如 linux 下可以是 10M,而 go 协程创建默认的调用栈大小是 2K
|
25
justicelove 2020-06-05 09:25:32 +08:00
就是内核线程和用户线程的区别, java1.2 之前也是用户线程,后来改成了内核线程
|
26
BingoXuan 2020-06-05 09:26:20 +08:00
现实当中,裸机跑程序效率最高的。加入 OS 这么方便的一层后效率就低很多了。所以要高效最好不要让操作系统重新调度资源。因而在操作系统最小调度单位线程下,通过协程做并发处理好过用操作系统线程调度。如果进程是厂房,线程是流水线,那么协程流水线自动适应生产不同产品,避免闲置流水线或新建流水线来生产。让 n 条流水线生产出 m 种产品( m>n )。
|
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 希望对你有帮助(里面还有上百套笔试题,很多都是我面试亲自遇到收集的)! 另外, 我的想法是 还是得深入研究下 操作系统..(毕业多年,已完全忘光) |
28
ylsc633 2020-06-05 09:51:20 +08:00 1
对了 记得看下这个概念 http://interview.wzcu.com/Golang/morestack.html
|
29
keshawnvan 2020-06-05 10:08:37 +08:00
1.堆栈刚开始比较小
2.比起线程去掉了线程局部存储 |
31
Philippa 2020-06-05 10:53:54 +08:00
大家看看第一个回答的视频,一个个技术要求来不断进化设计,看 20 分钟就好了,后面太多细节。就是那貌似是俄语口音的英语也只有自动英语字幕,不容易听。
|
32
mengzhuo 2020-06-05 11:04:55 +08:00
1. 线程没这么多,一个 Goroutine 现在是 384 字节
2. 轻量是因为,线程上下文切换不仅大,还需要 flush TBL |
33
haha370104 2020-06-05 11:11:56 +08:00
本质上就是单线程,我的个人理解是 runtime 机制带来的程序内的控制权转交
|
35
yukiloh 2020-06-05 13:44:40 +08:00
一楼一上来是个空白,吓了我一跳..
|
37
liuxingdeyu 2020-06-05 15:35:13 +08:00
在想一个问题,是不是还有个原因,就是协程使用的时间成本相对小于线程,因为协程的时间粒度更小
|
38
lff0305 2020-06-05 16:10:26 +08:00
@sagaxu 没这么大, C/C++ call createThread api 的时候可以设置栈大小, 默认好像是 1M
早几年记得 Java 好像是 512k 默认, 后来改成 128k 了 (可以用 -Xss 设置) |
39
xsen 2020-06-05 16:33:29 +08:00 1
1. 协程是轻量级的线程,就是说资源占用少很多
2. 可以认为协程是用户态的轻量级线程,不会涉及到上下文切换——那自然效率更高、性能更好 这个类似用户态的网络协议栈,一样的道理 其实从模型上来说,协程的底层机制类似基于 mq 的任务分发机制;只是协程底层针对一般的 mq 任务分发,多做了一层虚拟资源的调度中心(调度逻辑 cpu 资源) |
40
xsen 2020-06-05 16:39:11 +08:00
也就是把一个线程当成一个逻辑 cpu,然后 go 做了一个基于时间片的调度中心,最小任务是协程
因为都是在用户态(单个线程),所以资源消耗比线程小,没有上下文切换 所以性能可以更好 |
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 模型,所以调度效率较高,使用的资源较少 |