😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go视界
    • Go小考
    • Go实战
  • Go资源

    • 优质课程
    • 在线宝典
    • 资源下载
    • 帮找资源
训练营 🔥
  • Go体系课&实战训练营
  • 升值加薪陪跑训练营
Go求职
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
  • 求职服务

    • 内推互助
    • 求职助力
    • 内推公司
Go友会
  • 城市
  • 校园
推广返佣
  • 返佣排行
  • 返佣规则
  • 推广学院
实验区
  • Go周边
  • Go宝典

    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
更多
  • 用户中心

    • 我的信息
    • 我的返佣
    • 我的消息
  • 玩转星球

    • 星球介绍
    • 星主权益
    • 吐槽专区
    • 成长记录
  • 合作交流

    • 商务合作
    • 讲师招募
    • 生态伙伴
  • Go真实面试题汇总系列

    • Go基础篇
  • 宝典内容

    • 20. 2个协程交替打印字母和数字
    • 21. goroutine与线程的区别?
    • 27. 为什么不要大量使用goroutine
    • 38. 协程goroutine
    • 41. go 中用 for 遍历多次执行 goroutine会存在什么问题
    • 49. 问等待所有goroutine结束,怎么做?
    • 56. goroutine 为什么轻量
    • 86. 用Channel和两个协程实现数组相加
    • 98. go协程的实现方式
    • 106. 并行goroutine如何实现
    • 109. goroutine和线程的区别,为什么说goroutine轻量
    • 117. 父 goroutine 退出,如何使得子 goroutine 也退出?
    • 125. 主协程如何等待其余协程完再操作
    • 141. 为什么不要频繁创建和停止goroutine
    • 146. 如何拿到多个goroutine的返回值,如何区别他们
    • 151.golang goroutine的工作原理以及他们怎么进行数据交互的
    • 157. 一个线程打印奇数一个线程打印偶数 交替打印
    • 160. channel主要做什么事情
    • 167. golang 协程机制
    • 168. 协程的栈空间大小有限制吗?会主动扩展吗?
    • 169. 用go实现一个协程池,大概用什么实现
    • 170. go里面为什么需要多协程?
    • 171. goroutine为什么会存在,为什么不使用线程?
    • 173. go协程线程进程区别
    • 176. go协程相比其它协程库区别在哪?
    • 184. 编程go协程交叉顺序打印数组
    • 185. go协程通信
    • 203. 一个goroutine sleep了,操作系统是怎么唤醒的
    • 222. Go的协程可以不可以自己让出cpu
    • 223. Go的协程可以只挂在一个线程上面吗
    • 239. 用go撸一个生产者消费型,用channel通信,怎么友好的关闭chan?
    • 240. goroutine调度源码
    • 248. go实现协程池
    • 249. 两个协程交替打印1到20
    • 251. goroutine 和 kernel thread 之间是什么关系?
    • 254. 用过go,那么进程,协程,线程各自的优缺点
    • 264. Go的多线程
    • 268. 进程和协程
    • 269. 如何解决孤儿进程的出现
    • 274. goroutine为什么比线程开销小,实现原理
    • 277. 协程实现顺序打印123
    • 286. goroutine的调度是出现在什么情况下,调度时做了什么
    • 297. golang有什么提高性能的设计, 重点说说goroutine
    • 298. 进程、线程和协程和通信方式
    • 300. Goroutine 数量是越多越好吗?
    • 308. go协程的简单用法
    • 314. 为什么用户级别的线程 goroutine 比操作系统线程更轻量级?
    • 341. 协程怎么停顿?
    • 367. go 如何关闭goroutine
    • 373. Go 语言协程怎么跑的
    • 375. Go创建协程的过程
    • 376. 协程共享哪些资源?
    • 385. go中协程是如何实现的
    • 386. 协程中参数直接使用,和传参的区别是什么,为什么会造成这种结果
    • 389. 是否写过go语言多协程内容
    • 394. 开俩个协程,一个协程生产数据,另一个协程对数据进行处理,处理完后再把数据发回去,使用管道如何实现?
    • 406. goroutine泄露
    • 407. 如何停止一个goroutine
    • 410. 查看goroutine (579)
    • 420. 用go协程的时候也是要走IO的,go是如何处理的?
    • 444. 有没有了解过goroutine的底层数据结构, 为什么协程比线程轻量且快
    • 461. 如何限制 goroutine 并发数量 (channel 或 WaitGroup)
    • 465. Go里面一个协程能保证绑定在一个内核线程上面的。

😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

444. 有没有了解过goroutine的底层数据结构, 为什么协程比线程轻量且快


企业题库解析小组

题目来源:小雨伞保险

答案:ORVR

底层数据结构

G是goroutine的缩写,相当于操作系统中的进程控制块,在这里就是goroutine的控制结构,是对goroutine的抽象。其中包括goid是这个goroutine的ID,status是这个goroutine的状态,如Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead等。

struct G{uintptr stackguard; // 分段栈的可用空间下界
uintptr stackbase; // 分段栈的栈基址
Gobuf sched; //进程切换时,利用sched域来保存上下文
uintptr stack0;FuncVal* fnstart; // goroutine运行的函数
void* param; // 用于传递参数,睡眠时其它
goroutine设置param,唤醒时此goroutine可以获取
int16 status; // 状态Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
int64 goid; // goroutine的id号G* schedlink;
M* m; // for debuggers, but offset not hard-coded
M* lockedm; // G被锁定只能在这个m上运行
uintptr gopc; // 创建这个goroutine的go表达式的pc...};
1
2
3
4
5
6
7
8
9
10
11

结构体G中的部分域如上所示。可以看到,其中包含了栈信息stackbase和stackguard,有运行的函数信息fnstart。这些就足够成为一个可执行的单元了,只要得到CPU就可以运行。goroutine切换时,上下文信息保存在结构体的sched域中。goroutine是轻量级的线程或者称为协程,切换时并不必陷入到操作系统内核中,所以保存过程很轻量。看一下结构体G中的Gobuf,其实只保存了当前栈指针,程序计数器,以及goroutine自身。

struct Gobuf
{
    // The offsets of these fields are known to (hard-coded in) libmach.
    uintptr sp;
    byte* pc;
    G* g;
    ...
};
1
2
3
4
5
6
7
8

记录g是为了恢复当前goroutine的结构体G指针,运行时库中使用了一个常驻的寄存器extern register G* g,这个是当前goroutine的结构体G的指针。这样做是为了快速地访问goroutine中的信息,比如,Go的栈的实现并没有使用%ebp寄存器,不过这可以通过g->stackbase快速得到。”extern register”是由6c,8c等实现的一个特殊的存储。在ARM上它是实际的寄存器;其它平台是由段寄存器进行索引的线程本地存储的一个槽位。在linux系统中,对g和m使用的分别是0(GS)和4(GS)。需要注意的是,链接器还会根据特定操作系统改变编译器的输出,例如,6l/linux下会将0(GS)重写为-16(FS)。每个链接到Go程序的C文件都必须包含runtime.h头文件,这样C编译器知道避免使用专用的寄存器。

为什么比线程快

可以从三个角度区别:内存消耗、创建与销毀、切换。

内存占用

创建一个 goroutine 的栈内存消耗为 2 KB,实际运行过程中,如果栈空间不够用,会自动进行扩容。创建一个 thread 则需要消耗 1 MB 栈内存,而且还需要一个被称为 “a guard page” 的区域用于和其他 thread 的栈空间进行隔离。对于一个用 Go 构建的 HTTP Server 而言,对到来的每个请求,创建一个 goroutine 用来处理是非常轻松的一件事。而如果用一个使用线程作为并发原语的语言构建的服务,例如 Java 来说,每个请求对应一个线程则太浪费资源了,很快就会出 OOM 错误(OutOfMermoryError)。

创建和销毀

Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。而 goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。

切换

当 threads 切换时,需要保存各种寄存器,以便将来恢复:16 general purpose registers, PC (Program Counter), SP (Stack Pointer), segment registers, 16 XMM registers, FP coprocessor state, 16 AVX registers, all MSRs etc.而 goroutines 切换只需保存三个寄存器:Program Counter, Stack Pointer and BP。一般而言,线程切换会消耗 1000-1500 纳秒,一个纳秒平均可以执行 12-18 条指令。所以由于线程切换,执行指令的条数会减少 12000-18000。Goroutine 的切换约为 200 ns,相当于 2400-3600 条指令。因此,goroutines 切换成本比 threads 要小得多。