1.密码学开源库整理
2.go程序是怎样运行起来的?
3.golang 调试Debug
4.Ubuntu20.04 下手动编译安装gcc-6.3.0安装,多版本GCC 共存和切换
5.golang面试题库?
6.「2023必刷版」Golang高频面试题
密码学开源库整理
密码学开源库整理 维护一个密码学开源列表,旨在促进大家的共同学习与交流。持续更新中,欢迎投稿,贡献宝贵的公司介绍源码资源。基础密码库
C/C++ MIRACL Crypto SDK- 一个广泛认可的多精度整数和有理数加密库,被视作椭圆曲线密码学的黄金标准。 OpenSSL- 用于传输层安全协议的健壮、商业级、功能齐全的开源工具包。 Tongsuo (原BabaSSL)- 提供现代密码学算法和安全通信协议的开源基础库,适用于各种业务场景。 NTL- 高性能、可移植的C++库,提供整数、向量、矩阵、多项式和浮点运算的数据结构和算法。 cryptoPP- 一个开源C++密码学库,包含了众多密码算法。 PBC- 一个基于GMP库的免费C库,用于执行基于配对的密码系统的数学运算。 NaCl- 一个易于使用的高效密码库,专为网络通信、加密、解密、签名等设计。 Sodium- NaCl的一个分支,具有兼容和扩展API,提供构建更高级加密工具所需的核心操作。 RELIC- 一个面向研究的现代密码原语工具箱,强调效率和灵活性。 OpenABE- 集成了各种基于属性的加密算法、行业标准加密功能和工具,易于使用。 cpabe toolkit- 实现基于密文策略的属性加密方案的程序,使用PBC库进行代数运算。 Paillier- 公钥密码系统,提供加法同态性,适用于保护隐私的应用。 代理重新加密- 公钥加密的一种形式,允许用户将其解密权委托给另一个用户。 BGW广播加密- 允许广播者向一组接收者发送加密信息的方案。JAVA
The Java Pairing-Based Cryptography Library (JPBC)- 一个开源密码工具箱,支持国密算法、数字证书和SSL/TLS安全通信协议。Python
pyUmbral- Umbral阈值代理重新加密方案的参考实现,支持密文委托。Golang
The Go Pairing-Based Cryptography Library- 提供不同SOTA函数式加密方案的实现。 CONIKS- 一个密钥管理系统,提供终端用户加密密钥的透明度和隐私保护。隐私增强技术库
mpc和FHE库- 包括ecc、paillier、elgamal等基础公钥密码算法。区块链与零知识证明
Rust/C++库- 实现zkSNARK方案的零知识证明系统。量子安全密码
liboqs- 一个开放源码C库,包含量子安全加密算法的开源实现。可搜索加密
收集的可搜索加密列表。隐私保护机器学习
收集的Secure Deep Learning代码库列表。 贡献者:go程序是怎样运行起来的?
本文基于 Go1..0 版本详细介绍 Go 语言程序的启动过程。首先,Go 程序启动顺序通常如下:理解 Go 中的 Runtime,分析 Go 的 Runtime 功能,确定程序入口点为 Runtime,深入分析 Runtime 实现,找到对应操作系统的 Go 语言程序启动入口,接着分析 runtime·rt0_go 函数,理解其作用,之后重点分析 runtime·check、runtime·args、runtime·osinit、runtime·schedinit、英雄联盟比赛源码runtime·newproc 和 runtime·mstart 函数。了解 Go 启动流程需要对 GMP 模型有一定了解。 Go 的启动流程主要包括以下几个关键步骤: 启动 Runtime:理解 Runtime 是 Go 的运行时环境,它包含内存管理、GC、协程和操作系统调用屏蔽等功能。 确定入口点:找到 Runtime 入口,通常位于 go 源码中 src/runtime 目录下的特定文件。 分析 runtime·rt0_go:这是 Go 语言运行时的入口点,负责设置和初始化运行时环境,然后创建 g0 和 m0 来运行程序的主函数。 深入细节:逐步分析 runtime·check、runtime·args、runtime·osinit、runtime·schedinit、runtime·newproc 和 runtime·mstart 函数,了解各自的作用。 全局变量初始化:理解全局变量的初始化主要发生在链接阶段,由编译器或链接器安排。 通过以上步骤,我们全面理解了 Go 语言程序的启动过程,以及启动流程中的关键组件和函数。深入细节分析有助于开发人员更好地掌握 Go 程序的执行机制。golang 调试Debug
GODEBUG变量在Golang中提供个参数,通过在go run命令中设置GODEBUG变量启用。这些参数并无常量标识,其含义通过代码硬编码实现。具体参数如下:
一、GODEBUG变量介绍
GODEBUG变量支持个参数。这些参数在runtime包的doc中有所介绍,并在schedinit()方法的调度器初始化中通过parsedebugvars()函数进行初始化。
二、垃圾回收分析:gctrace
gctrace用于分析Golang程序的垃圾回收信息。
命令:设置GODEBUG=gctrace=1
输出:详细记录Golang程序的垃圾回收过程信息
释义:通过此设置,可以追踪并记录Golang程序中垃圾回收的详细信息,帮助开发者理解和优化内存管理。
三、GMP调度跟踪:schedtrace和scheddetail
这些功能由Golang自身提供,但其功能较为有限,建议使用更专业的工具如GDB或dlv等进行更深入的分析。
命令:设置GODEBUG=schedtrace=1 或 GODEBUG=scheddetail=1
输出:具体调度过程信息
释义:通过启用schedtrace或scheddetail,可以获取Golang程序调度器的详细运行信息,有助于深入理解程序执行过程中的调度策略。
四、GMP跟踪细节信息
具体命令:设置GODEBUG=mptrace=1
输出:详细记录Golang多处理器环境下的线程执行信息
协程状态(详细信息查看源码:golang.org/src/runtime/...)
释义:此设置用于追踪Golang程序中协程的执行状态和多处理器环境下的线程交互信息,提供对程序并发执行过程的深入洞察。
Ubuntu. 下手动编译安装gcc-6.3.0安装,多版本GCC 共存和切换
在Ubuntu .环境下,为编译Matlab的mex函数,需要特定版本的gcc-6.3.0。尽管sudo apt-get默认安装的是6.5.0,而较新版本的如gcc-8, gcc-9, gcc-可以通过apt-get安装。以下是手动编译和安装gcc-6.3.0,以及处理多版本GCC共存和切换的步骤:
1. 首先,你需要从ftp.gnu.org/gnu/gcc/下载gcc-6.3.0的源代码。
2. 安装时,确保使用自定义路径,如--with-gmp=$HOME/local/ --with-mpfr=$HOME/local/ --with-cgal=$HOME/local/,这与你的安装目录相关。
3. 配置和编译过程可能耗时且可能出现错误,例如configure期间可能遇到`CC' has changed since the previous run`的错误,解决方法是运行`make distclean`或`rm ./config.cache`重置。
4. 在遇到`error: dereferencing pointer to incomplete type 'struct ucontext'`错误时,这是由于struct定义不完整引起的。需要修正相关变量uc_的代码。
5. 另一个问题是关于`sanitizer_platform_limits_posix.cc`中的sys/ustat.h文件问题,需要在适当位置插入预计算的Linux结构ustat大小。
6. 在sanitizer_common/sanitizer_internal_defs.h文件中,可能会遇到数组大小负数的错误。在configure时,可以考虑注释掉--disable-libsanitizer以解决这个问题,特别是淘宝店铺html源码如果你不使用golang。
总之,手动编译gcc-6.3.0在Ubuntu .上需要细心处理各种编译时的问题,并且需要根据错误信息进行相应的调整,同时要处理不同GCC版本的共存和切换。
golang面试题库?
go面试题整理(附带部分自己的解答)
原文:
如果有解答的不对的,麻烦各位在评论写出来~
go的调度原理是基于GMP模型,G代表一个goroutine,不限制数量;M=machine,代表一个线程,最大1万,所有G任务还是在M上执行;P=processor代表一个处理器,每一个允许的M都会绑定一个G,默认与逻辑CPU数量相等(通过runtime.GOMAXPROCS(runtime.NumCPU())设置)。
go调用过程:
可以能,也可以不能。
因为go存在不能使用==判断类型:map、slice,如果struct包含这些类型的字段,则不能比较。
这两种类型也不能作为map的key。
类似栈操作,后进先出。
因为go的return是一个非原子性操作,比如语句returni,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。
select的case的表达式必须是一个channel类型,所有case都会被求值,求值顺序自上而下,从左至右。如果多个case可以完成,则会随机执行一个case,如果有default分支,则执行default分支语句。如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。
break关键字可跳出select的执行。
goroutine管理、信息传递。context的意思是上下文,在线程、协程中都有这个概念,它指的是程序单元的一个运行状态、现场、快照,包含。context在多个goroutine中是并发安全的。
应用场景:
例子参考:
waitgroup
channel
len:切片的长度,访问时间复杂度为O(1),go的slice底层是对数组的引用。
cap:切片的容量,扩容是以这个值为标准。默认扩容是2倍,当达到的长度后,按1.倍。
扩容:每次扩容slice底层都将先分配新的容量的内存空间,再将老的数组拷贝到新的内存空间,因为这个操作不是并发安全的。所以并发进行append操作,读到内存中的老数组可能为同一个,最终导致append的数据丢失。
共享:slice的底层是对数组的引用,因此如果两个切片引用了同一个数组片段,就会形成共享底层数组。当sliec发生内存的重新分配(如扩容)时,会对共享进行隔断。详细见下面例子:
make([]Type,len,cap)
map的底层是hashtable(hmap类型),对key值进行了hash,如何导入servlet源码并将结果的低八位用于确定key/value存在于哪个bucket(bmap类型)。再将高八位与bucket的tophash进行依次比较,确定是否存在。出现hash冲撞时,会通过bucket的overflow指向另一个bucket,形成一个单向链表。每个bucket存储8个键值对。
如果要实现map的顺序读取,需要使用一个slice来存储map的key并按照顺序进行排序。
利用map,如果要求并发安全,就用sync.map
要注意下set中的delete函数需要使用delete(map)来实现,但是这个并不会释放内存,除非value也是一个子map。当进行多次delete后,可以使用make来重建map。
使用sync.Map来管理topic,用channel来做队列。
参考:
多路归并法:
preclass="vditor-reset"placeholder=""contenteditable="true"spellcheck="false"pdata-block="0"(1)假设有K路ahref=""数据流/a,流内部是有序的,且流间同为升序或降序;
/ppdata-block="0"(2)首先读取每个流的第一个数,如果已经EOF,pass;
/ppdata-block="0"(3)将有效的k(k可能小于K)个数比较,选出最小的那路mink,输出,读取mink的下一个;
/ppdata-block="0"(4)直到所有K路都EOF。
/p/pre
假设文件又1个G,内存只有M,无法将1个G的文件全部读到内存进行排序。
第一步:
可以分为段读取,每段读取M的数据并排序好写入硬盘。
假设写入后的文件为A,B,C...
第二步:
将A,B,C...的第一个字符拿出来,对这个字符进行排序,并将结果写入硬盘,同时记录被写入的字符的文件指针P。
第三步:
将刚刚排序好的9个字符再加上从指针P读取到的P+1位数据进行排序,并写入硬盘。
重复二、三步骤。
go文件读写参考:
保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同的排序叫稳定排序。
快速排序、希尔排序、堆排序、直接选择排序不是稳定的排序算法。
基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。
参考:
head只请求页面的首部。多用来判断网页是否被修改和超链接的有效性。
get请求页面信息,并返回实例的主体。
参考:
:未授权的访问。
:拒绝访问。
普通的.ipv4.tcp_keepalive_intvl=//当探测没有确认时,重新发送探测的频度。缺省是秒。
net.ipv4.tcp_keepalive_probes=9//在认定连接失效之前,发送多少个TCP的keepalive探测包。缺省值是9。这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之后可以有多少时间没有回应
net.ipv4.tcp_keepalive_time=//当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。一般设置为分钟
修改:
可以
tcp是面向连接的,upd是无连接状态的。
udp相比tcp没有建立连接的过程,所以更快,同时也更安全,微网站源码修改不容易被攻击。upd没有阻塞控制,因此出现网络阻塞不会使源主机的发送效率降低。upd支持一对多,多对多等,tcp是点对点传输。tcp首部开销字节,udp8字节。
udp使用场景:视频通话、im聊天等。
time-wait表示客户端等待服务端返回关闭信息的状态,closed_wait表示服务端得知客户端想要关闭连接,进入半关闭状态并返回一段TCP报文。
time-wait作用:
解决办法:
close_wait:
被动关闭,通常是由于客户端忘记关闭tcp连接导致。
根据业务来啊~
重要指标是cardinality(不重复数量),这个数量/总行数如果过小(趋近于0)代表索引基本没意义,比如sex性别这种。
另外查询不要使用select*,根据select的条件+where条件做组合索引,尽量实现覆盖索引,避免回表。
僵尸进程:
即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。
孤儿进程:
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。
但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。
原文链接:
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免方法:
端口占用:lsof-i:端口号或者nestat
cpu、内存占用:top
发送信号:kill-l列出所有信号,然后用kill[信号变化][进程号]来执行。如kill-。强制杀死进程
gitlog:查看提交记录
gitdiff:查看变更记录
gitmerge:目标分支改变,而源分支保持原样。优点:保留提交历史,保留分支结构。但会有大量的merge记录
gitrebase:将修改拼接到最新,复杂的记录变得优雅,单个操作变得(revert)很简单;缺点:
gitrevert:反做指定版本,会新生成一个版本
gitreset:重置到某个版本,中间版本全部丢失
etcd、Consul
pprof
节省空间(非叶子节点不存储数据,相对btree的优势),减少I/O次数(节省的空间全部存指针地址,让树变的矮胖),范围查找方便(相对hash的优势)。
explain
其他的见:
runtime2.go中关于p的定义:其中runnext指针决定了下一个要运行的g,根据英文的注释大致意思是说:
所以当设置runtime.GOMAXPROCS(1)时,此时只有一个P,创建的g依次加入P,当最后一个即i==9时,加入的最后一个g将会继承当前主goroutinue的剩余时间片继续执行,所以会先输出9,之后再依次执行P队列中其它的g。
方法一:
方法二:
[上传失败...(image-4ef-)]
方法1:to_days,返回给的日期从0开始算的天数。
方法2:data_add。向日期添加指定时间间隔
[上传失败...(image-bb-)]
面试问题总结(一)Golang
使用go语言的好处:go语言的设计是务实的,go在针对并发上进行了优化,并且支持大规模高并发,又由于单一的码格式,相比于其他语言更具有可读性,在垃圾回收上比java和Python更有效,因为他是和程序同时执行的.
1.进程,线程,协程的区别,协程的优势
2.讲一下GMP模型(重点)
3.Go的GC,混合写屏障(重点)
4.go的Slice和数组的区别,slice的扩容原理(重点)
5.讲一下channel,实现原理(重点)
6.讲一下Go的Map的实现原理,是否线程安全,如何实现安全(重点)
7.new和make的区别
8.说一下内存逃逸
9.函数传指针和传值有什么区别
.goroutine之间的通信方式
.测试是怎么做的(单元测试,压力测试)
.堆和栈的区别
golang面试题2之判断字符串中字符是否全都不同请实现个算法,确定个字符串的所有字符是否全都不同。这我们要求不允
许使额外的存储结构。给定个string,请返回个bool值,true代表所有字符全都
不同,false代表存在相同的字符。保证字符串中的字符为ASCII字符。字符串的
度于等于。
这有个重点,第个是ASCII字符,ASCII字符字符共有个,其中个是常
字符,可以在键盘上输。之后的是键盘上法找到的。
然后是全部不同,也就是字符串中的字符没有重复的,再次,不准使额外的储存结
构,且字符串于等于。
如果允许其他额外储存结构,这个题很好做。如果不允许的话,可以使golang内置
的式实现。
通过strings.Count函数判断:
使的是golang内置法strings.Count,可以来判断在个字符串中包含
的另外个字符串的数量
还有不同的方法同样可以实现,你了解吗?
推荐go相关技术专栏
gRPC-go源码剖析与实战_带你走进gRPC-go的源码世界-CSDN博客
「必刷版」Golang高频面试题
在面试Go语言时,候选人应深入了解其特性,如切片与数组的差异,Goroutine与GMP模型,以及锁机制如Mutex和RWMutex的使用。Go语言以其优雅简洁和高效性能备受青睐,尤其在云时代项目中占据重要地位。面试中,常考知识点包括切片扩容机制的更新、互斥锁的并发控制、死锁的概念与解决、sync.Cond的通信机制、Channel的通信原理和SingleFlight的并发限制。掌握这些内容不仅需要理论理解,还需结合源码阅读,例如Go源码的详细注释。此外,低代码工具如JNPF快速开发平台在企业内部工具开发中的应用,也是技术发展中的一个值得关注的趋势,可以提高开发效率。以下是对高频面试题的概述:
1. **Go语言特性**:Go语言以其高效与简洁著称,结合了C++的性能和Java的库管理。项目如Docker和Kubernetes展示了其在云时代的优势。
2. **面试重点**:深入了解Go语言,包括切片、通道、异常处理、Goroutine、GMP模型、字符串拼接、指针、反射、接口、sync和测试工具等。
3. **实例解析**:
- **切片与数组**:数组用于固定长度,切片则更灵活,尤其适合动态数据。
- **切片扩容机制**:Go 1.后,切片扩容策略更为平滑。
- **Mutex**:Go的互斥锁,用于并发资源保护,有正常模式和饥饿模式。
- **RWMutex**:适用于读多写少的场景,处理读写锁。
- **死锁**:理解死锁产生的条件和解决策略。
- **sync.Cond**:用于等待条件的goroutine通信。
- **Channel**:Go的并发通信机制,用于goroutine间通信。
- **SingleFlight**:并发控制原语,避免重复调用。
4. **技术前沿**:低代码工具如JNPF快开发平台,提高开发效率,减少重复工作。
Golang面试题从浅入深高频必刷「版」
大家好,我是阳哥,专注于Go语言的学习分享和就业指导。Go语言以其优雅简洁的特点脱颖而出,兼顾了C++的高性能和Java/Python的便利性,拥有接口、垃圾回收和goroutine等独特设计,尤其在云时代,Go的应用项目如Docker、Kubernetes等众多优秀案例证明了其价值。 在面试中,深入理解Go语言特性是关键,例如切片与数组的差异、通道(Goroutine的通信机制)、异常处理、GMP模型、字符串高效拼接等知识点。掌握Go源码,尤其是其详细注释,能有效提升技能。同时,熟悉go test和相关工具链也是必修课。1. Go语言基础
切片与数组:Go中的数组适用于固定长度,而切片更灵活,常用于动态长度数据。理解切片的初始化、长度、容量操作以及扩容机制,包括不同Go版本的差异。
Mutex与RWMutex:互斥锁在并发控制中至关重要,理解Mutex的基本操作和饥饿模式,以及RWMutex在读写场景中的应用。
死锁与同步工具:了解死锁的概念及解决策略,以及sync.Cond在条件等待中的应用和易错点。
Channel:作为并发通信的核心,理解Channel的用法、底层结构和常见应用场景。
进阶主题
SingleFlight:Go中的并发控制扩展,与sync.Once的区别,以及在优化系统性能中的作用和使用方法。
Golang GMP 原理
通常语义中的线程,指的是内核级线程,核心点包括:(1)它是操作系统最小调度单元;(2)创建、销毁、调度交由内核完成,cpu 需完成用户态与内核态间的切换;(3)可充分利用多核,实现并行。
协程,又称为用户级线程,核心点如下:(1)与线程存在映射关系,为 M:1;(2)创建、销毁、调度在用户态完成,对内核透明,所以更轻;(3)从属同一个内核级线程,无法并行;一个协程阻塞会导致从属同一线程的所有协程无法执行。
Goroutine,经 Golang 优化后的特殊“协程”,核心点包括:(1)与线程存在映射关系,为 M:N;(2)创建、销毁、调度在用户态完成,对内核透明,足够轻便;(3)可利用多个线程,实现并行;(4)通过调度器的斡旋,实现和线程间的动态绑定和灵活调度;(5)栈空间大小可动态扩缩,因地制宜。
对比三个模型的各项能力:综上,goroutine 可说是博采众长之物。
实际上,“灵活调度” 一词概括得实在过于简要,Golang 在调度 goroutine 时,针对“如何减少加锁行为”,“如何避免资源不均”等问题都给出了精彩的解决方案,这一切都得益于经典的 “gmp” 模型。
GMP = goroutine + machine + processor(+ 一套有机组合的机制),下面先单独拆出每个组件进行介绍,最后再总览全局,对 GMP 进行总述。
G = goroutine,是 Golang 中对协程的抽象;(2)g 有自己的运行栈、状态、以及执行的任务函数(用户通过 go func 指定);(3)g 需要绑定到 p 才能执行,在 g 的视角中,p 就是它的 cpu。
P = processor,是 Golang 中的调度器;(2)p 是 gmp 的中枢,借由 p 承上启下,实现 g 和 m 之间的动态有机结合;(3)对 g 而言,p 是其 cpu,g 只有被 p 调度,才得以执行;(4)对 m 而言,p 是其执行代理,为其提供必要信息的同时(可执行的 g、内存分配情况等),并隐藏了繁杂的调度细节;(5)p 的数量决定了 g 最大并行数量,可由用户通过 GOMAXPROCS 进行设定(超过 CPU 核数时无意义)。
M = machine,是 Golang 中对线程的抽象;(1)m 不直接执行 g,而是先和 p 绑定,由其实现代理;(3)借由 p 的存在,m 无需和 g 绑死,也无需记录 g 的状态信息,因此 g 在全生命周期中可以实现跨 m 执行。
全局有多个 M 和多个 P,但同时并行的 G 的最大数量等于 P 的数量。G 的存放队列有三类:P 的本地队列;全局队列;和 wait 队列(图中未展示,为 io 阻塞就绪态 goroutine 队列)。
M 调度 G 时,优先取 P 本地队列,其次取全局队列,最后取 wait 队列。这样的好处是,取本地队列时,可以接近于无锁化,减少全局锁竞争。为防止不同 P 的闲忙差异过大,设立 work-stealing 机制,本地队列为空的 P 可以尝试从其他 P 本地队列偷取一半的 G 补充到自身队列。
核心数据结构定义于 runtime/runtime2.go 文件中,各个类的成员属性较多,这里只摘取核心字段进行介绍:g 的生命周期由以下几种状态组成:_Gidle(值为 0,为协程开始创建时的状态,此时尚未初始化完成);_Grunnable(值为 1,协程在待执行队列中,等待被执行);_Grunning(值为 2,协程正在执行,同一时刻一个 p 中只有一个 g 处于此状态);_Gsyscall(值为 3,协程正在执行系统调用);_Gwaiting(值为 4,协程处于挂起态,需要等待被唤醒. gc、channel 通信或者锁操作时经常会进入这种状态);_Gdead(值为 6,协程刚初始化完成或者已经被销毁,会处于此状态);_Gcopystack(值为 8,协程正在栈扩容流程中);_Greempted(值为 9,协程被抢占后的状态)。
文字性总结难免有些过于含糊和空洞,对一些细节的描述总是不够精确的。下面照旧开启源码走读流程,从代码中寻求理论证明和细节补充。
gmp 数据结构定义为 runtime/runtime2.go 文件中,由于各个类的成员属性较多,那么只摘取核心字段进行介绍:(1)m:在 p 的代理,负责执行当前 g 的 m;(2)sched.sp:保存 CPU 的 rsp 寄存器的值,指向函数调用栈栈顶;(3)sched.pc:保存 CPU 的 rip 寄存器的值,指向程序下一条执行指令的地址;(4)sched.ret:保存系统调用的返回值;(5)sched.bp:保存 CPU 的 rbp 寄存器的值,存储函数栈帧的起始位置。其中 g 的生命周期由以下几种状态组成:(1)_Gidle(值为 0,为协程开始创建时的状态,此时尚未初始化完成);(2)_Grunnable(值为 1,协程在待执行队列中,等待被执行);(3)_Grunning(值为 2,协程正在执行,同一时刻一个 p 中只有一个 g 处于此状态);(4)_Gsyscall(值为 3,协程正在执行系统调用);(5)_Gwaiting(值为 4,协程处于挂起态,需要等待被唤醒. gc、channel 通信或者锁操作时经常会进入这种状态);(6)_Gdead(值为 6,协程刚初始化完成或者已经被销毁,会处于此状态);(7)_Gcopystack(值为 8,协程正在栈扩容流程中);(8)_Greempted(值为 9,协程被抢占后的状态)。
其中,goroutine 的类型可分为两类:(1)I 负责调度普通 g 的 g0,执行固定的调度流程,与 m 的关系为一对一;(2)II 负责执行用户函数的普通 g。m 通过 p 调度执行的 goroutine 永远在普通 g 和 g0 之间进行切换。
主动调度是用户主动执行让渡的方式,主要方式是,用户在执行代码中调用了 runtime.Gosched 方法,此时当前 g 会当让出执行权,主动进行队列等待下次被调度执行。被动调度因当前不满足某种执行条件,g 可能会陷入阻塞态无法被调度,直到关注的条件达成后,g 才从阻塞中被唤醒,重新进入可执行队列等待被调度。
正常调度指的是 g 中的执行任务已完成,g0 会将当前 g 置为死亡状态,发起新一轮调度。抢占调度指的是 g 执行系统调用超过指定的时长,且全局的 p 资源比较紧缺,此时将 p 和 g 解绑,抢占出来用于其他 g 的调度。
调度流程的主干方法是位于 runtime/proc.go 中的 schedule 函数。在宏观调度流程中,我们可以尝试对 gmp 的宏观调度流程进行整体串联,包括:(1)以 g0 -> g -> g0 的一轮循环为例进行串联;(2)g0 执行 schedule() 函数,寻找到用于执行的 g;(3)g0 执行 execute() 方法,更新当前 g、p 的状态信息,并调用 gogo() 方法,将执行权交给 g;(4)g 因主动让渡(gosche_m())、被动调度(park_m())、正常结束(goexit0())等原因,调用 m_call 函数,执行权重新回到 g0 手中;(5)g0 执行 schedule() 函数,开启新一轮循环。
在 Golang 中,调度流程的主干方法是位于 runtime/proc.go 中的 schedule 函数,此时的执行权位于 g0 手中。在 findRunnable 方法中,调度流程中,一个非常核心的步骤就是为 m 寻找到下一个执行的 g。在 execute 方法中,当 g0 为 m 寻找到可执行的 g 之后,接下来就开始执行 g。
在 g 执行主动让渡时,会调用 mcall 方法将执行权归还给 g0,并由 g0 调用 gosched_m 方法。在 g 需要被动调度时,会调用 mcall 方法切换至 g0,并调用 park_m 方法将 g 置为阻塞态。当 g 执行完成时,会先执行 mcall 方法切换至 g0,然后调用 goexit0 方法。与 g 的系统调用有关的,视角切换回发生系统调用前,与 g 绑定的原 m 当中,此时执行权同样位于 m 的 g0 手中。在 m 需要执行系统调用前,会先执行位于 runtime/proc.go 的 reentersyscall 的方法。当 m 完成了内核态的系统调用之后,此时会步入位于 runtime/proc.go 的 exitsyscall 函数中。
与 g 的系统调用有关的,视角切换回发生系统调用前,与 g 绑定的原 m 当中,在 m 需要执行系统调用前,会先执行位于 runtime/proc.go 的 reentersyscall 的方法。当 m 完成了内核态的系统调用之后,此时会步入位于 runtime/proc.go 的 exitsyscall 函数中。
当 g 执行完成时,会先执行 mcall 方法切换至 g0,然后调用 goexit0 方法。当 m 完成了内核态的系统调用之后,此时会步入位于 runtime/proc.go 的 exitsyscall 函数中。
对于抢占调度的执行者,不是 g0,而是一个全局的 monitor g,代码位于 runtime/proc.go 的 retake 方法中。与 g 的系统调用有关的,视角切换回发生系统调用前,与 g 绑定的原 m 当中,在 m 需要执行系统调用前,会先执行位于 runtime/proc.go 的 reentersyscall 的方法。当 m 完成了内核态的系统调用之后,此时会步入位于 runtime/proc.go 的 exitsyscall 函数中。
在 Golang 中,调度流程的主干方法是位于 runtime/proc.go 中的 schedule 函数,此时的执行权位于 g0 手中。在 findRunnable 方法中,调度流程中,一个非常核心的步骤就是为 m 寻找到下一个执行的 g。在 execute 方法中,当 g0 为 m 寻找到可执行的 g 之后,接下来就开始执行 g。
在 g 执行主动让渡时,会调用 mcall 方法将执行权归还给 g0,并由 g0 调用 gosched_m 方法。在 g 需要被动调度时,会调用 mcall 方法切换至 g0,并调用 park_m 方法将 g 置为阻塞态。当 g 执行完成时,会先执行 mcall 方法切换至 g0,然后调用 goexit0 方法。当 m 完成了内核态的系统调用之后,此时会步入位于