几个关于 Go Runtime 的问题

2021-02-04 22:59:30 +08:00
 Dongxiem

问题:go 的 runtime 如何实现?(这个实在不理解)

追问:runtime 这个进程是运行在哪儿的?

追问:每个 go 进程都会有一个吗还是共用一个?(应该是共用一个)

追问:如果你运行了 go 进程,在 linux 系统里查看进程能看到 runtime 进程吗?

4462 次点击
所在节点    Go 编程语言
60 条回复
saberlong
2021-02-04 23:09:16 +08:00
golang 的 runtime 源码在 src 可以直接找到。真正的 main 函数其实是在 runtime 里。然后你可以看到启动的步骤,包括不限于启动 g0 协程和启动 gc 工作。
每个 go 进程是自己独立的,和虚拟机区分开来。没有独立的 runtime 进程。
简单的讲,你可以理解成 c 语言加入了协程和 gc 的框架。由于有 gc,所以不能直接使用系统分配内存的函数,都是通过这个框架提供的函数来分配。
Jirajine
2021-02-04 23:11:50 +08:00
runtime 就是你编译出的程序中你自己的代码以外的部分。
比如一个 c 程序,不会直接执行 main,而是执行_start (多数平台),这个函数会先进行一些栈空间等初始化,然后再调用你的 main 函数。这个函数就是由 c 的 runtime 提供。
go 这样的语言 runtime 做的事情更多,初始化线程池、调度器、gc 、unwind 等,然后再把你的 main 函数作为一个 goroutine 启动。
saberlong
2021-02-04 23:12:34 +08:00
入口代码在 src/runtime/proc.go 。有个 func main()
PureWhiteWu
2021-02-04 23:13:15 +08:00
别的不说,如果这是面试,如果你都回复不知道 go runtime 怎么实现了,还追着问下面的三个问题,那这面试官妥妥 sb 。
1011
2021-02-04 23:15:57 +08:00
runtime 只是一个名字而已,本质只是一个“库”,go 程序编译时它将和我们编写的代码一同编译为一个可执行文件
golang.org/doc/faq#runtime
codehz
2021-02-04 23:37:31 +08:00
C 也有 runtime 呢,应该去想想 crt 怎么做的(别说人家叫 libc 就不算 crt 了)
C++也有 runtime,叫 libstdc++/libc++/vcruntime

C 的可以共享,也可以静态链接进去(
go 的就不行了,每个都自己带一份
henglinli
2021-02-05 02:57:24 +08:00
golang 的 runtime 其实是个线程池,用 work-steal 调度算法调度 goroutine 。golang 的实现已知的有 go gccgo llgo,gccgo 也是 go 的官方实现就是下面的 gofrontend,llgo 已经完了。参见 https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#heading=h.nfb3viti0vlt

runtime 运行在 OS 线程上,比如 pthread 。参见 https://github.com/golang/gofrontend/blob/master/libgo/runtime/go-libmain.c#L208

go 进程应该是 goroutine 吧。这个真不好回答。有很多个,但至少有一个 runtime.main,至于最多有多少个,如果 golang 实现者考虑过这个问题的话,会有一个可以设置的参数的,可以用环境变量 GOMAXPROCS 限制 OS 线程数。
比如:一个只有 http.ListenAndServe 的 pprof go 程序,有 3 个 goroutine,5 个 OS 线程。//go version go1.15.7 darwin/amd64

linux 里系统查看 go 进程是指 top 用查看 go 程序吧。top 查看到的是那个“线程池”,用 pprof 可以查看 goroutine 。
参见 https://golang.google.cn/pkg/net/http/pprof/
baiyi
2021-02-05 08:49:00 +08:00
runtime 不是完全独立于你程序的一个进程。相反,你写的程序是作为 goroutine 在 runtime 中的调度器中运行。当然 runtime 也不只有调度器,还有其他 GC 什么的程序在运行。
taowen
2021-02-05 08:55:10 +08:00
https://talkgo.org/t/topic/31 讲解 golang 的协程调度实现
lewis89
2021-02-05 10:04:40 +08:00
@henglinli #7 我补充一点吧

go runtime,严格来讲就是以 golang 下 以 goroutine 结合管道通信的并发模型,这个并发模型是基于 stackfull 的协程模型,相反的是 stackless 的协程模型,而传统其它语言可能是基于线程池以及锁的并发模型 ,有兴趣可以看 stackfull 跟 stackless 的区别,前者会占用空间,后者对内存空间友好,但是现在是 2021 年了,谁家老板买不起 2T 内存? golang 选的是 stackfull 协程模型,当然这里比较简单,下面会详细介绍,有兴趣深入还是根据我的关键字去找书看。

然后 stackfull 的本质就是 每个协程有自己的栈幁,具体我没有关注过 goroutine 的栈幁结构,但是从我观察 goroutine 进入内核态的汇编代码,做了一次 go 的调用约定 到 amd64 fastcall 的调用约定转换,基本上可以确认 golang 内部的函数调用约定以及栈幁结构 应该是平台无关化的,另外 golang 貌似是不支持二进制 lib 编译的,是不是因为内部大版本之间的 ABI 是否从来未稳定过。

然后弄这个 stackfull 的协程模型是因为 golang 这个语言的野心很大,它希望能把操作系统内核态任务调度这个事情全权交给应用态的 go 调度系统来作,而之前所有的语言 包括 C++ Java C 都是将调度模型交给操作系统提供的线程抽象,这一点上 golang 是一个伟大的进步 配合管道跟非阻塞式 IO 以及 epoll 调用,可以在用户态实现一个无锁且按需调度的调度系统,可以说 go 它是专门为后端服务设计的,当然从语法上来看,目前不支持泛型,我暂时没有加大对 go 这门语言的投入,但是它这个设计理念是好的,那就是带有运行时的语言应该从操作系统那里拿回原本属于我们应用开发人员的调度功能。

这里可以提出两点

1. 管道模型为什么优于锁 monitor ? 传统操作系统内核提供的监视器锁 存在惊群的问题,这是很难避免的,因为操作系统并不知道你要唤醒多少个线程,当然你也可以指定唤醒的数量,这里就要做到很精细化的锁唤醒操作,例如 Java 里面的 blockingQueue 当队列满了的时候 操作的线程会去 唤醒因为读等待的线程而不是去唤醒因为写等待的线程,当队列空了的时候,当前操作线程就会去唤醒因为写等待的线程而不是去唤醒因为读等待的线程,而之前我所说的这些操作,都需要对并发编程有很高的理解,你才能设计出一个多线程并发且线程安全的队列,这一点对开发人员要求很高,而且从因为监视器锁 Java 的线程会频繁进入内核 开销很大。

但如果使用管道,其实从 golang 调度器来看,当你想从一个管道读数据而挂起的时候,其实 golang 调度器只要把当前线程的几个寄存器换掉就能把对 CPU 的控制权转移到另外一个协程上,这中间无需进入内核调度,而且 golang 还可以做一些优化,把对 CPU 的控制器优先配给需要写这个管道的协程,或者把对同一个管道读写的协程都分配到一个 CPU 上,这样一来 golang 使用管道可以实现完全无锁化的协程之间的通信,但是从编写 golang 的协程代码的人来看,他的大脑负担就会少很多。
lewis89
2021-02-05 10:04:54 +08:00
第二点 我待会下午补充
lewis89
2021-02-05 10:06:30 +08:00
对于 C10K 问题
Dongxiem
2021-02-05 11:53:52 +08:00
谢谢以上大佬们的回复
love2020
2021-02-05 12:13:31 +08:00
@lewis89 听不懂 有点难了
lewis89
2021-02-05 13:08:36 +08:00
@love2020 #14 没事,多搜索就好了..

我分享的 可能广度 深度都有,
可能很多人 根本不知道 ABI monitor 监视器 栈幁 fastcall stackfuul stackless 这种东西,
你如果计算机体系结构 都了解的话,就不会产生这种疑惑了
1011
2021-02-05 13:19:03 +08:00
@lewis89 建议大佬整理一下单独成文👍🏿

我之前通过一段时间的学习,也觉得 go 的这种自建调度策略的做法非常优雅,但是再让我说为什么好就说不出了,最近也打算再深入一下
lewis89
2021-02-05 13:29:16 +08:00
@1011 #16 其实也不是优雅的问题,以前线程的通信,肯定使用监视器锁去做,这样 调度 同步都是内核来完成的,应用态的语言包括我们应用程序员都是没有改造权限的,golang 做的一个事情就是把原本让内核干的事情,都夺回来来让应用层自己来干,因为操作系统其实 提供像信号这种中断机制,就类似 CPU 提供给操作系统的时钟中断一样,也就是 go 调度器 干了原本归内核的事情.. 干了之后 我们就可以自己优化策略,或者用其它的抽象 例如管道 来替代锁这种抽象
Dongxiem
2021-02-05 13:42:30 +08:00
@taowen 夜读这一期是讲了调度模型,但是关于 runtime 的具体还是没有涉及到,比如上面几个问题似乎在这个视频当中找不到答案。
lewis89
2021-02-05 13:45:28 +08:00
@Dongxiem #18 runtime 当然 还有 垃圾回收器.. 忘记讲了 垃圾回收也是 runtime 一部分
Dongxiem
2021-02-05 13:46:58 +08:00
@henglinli
“runtime 运行在 OS 线程上,比如 pthread 。参见 https://github.com/golang/gofrontend/blob/master/libgo/runtime/go-libmain.c#L208

go 进程应该是 goroutine 吧。这个真不好回答。有很多个,但至少有一个 runtime.main,至于最多有多少个,如果 golang 实现者考虑过这个问题的话,会有一个可以设置的参数的,可以用环境变量 GOMAXPROCS 限制 OS 线程数。”

--------------------------

请问上述内容,是不是可以认为你觉得是一个 OS 线程就有一个 runtime 呢?

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

https://www.v2ex.com/t/751398

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

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

© 2021 V2EX