1.Go 1.22中值得关注的源码几个变化
2.Golang源码剖析panic与recover,看不懂你打我好了
3.go源码解析之TCP连接(六)——IO多路复用之事件注册
4.Go语言源码阅读分析(3)-- unsafe
5.Go的质量执行原理以及Go的命令
6.Go 1.20要来了,看看都有哪些变化-第2篇
Go 1.22中值得关注的源码几个变化
美国时间年2月6日,Go 1.版本正式发布,质量Go团队成员Eli Bendersky在Go官博宣布这一消息。源码
Go 1.版本在语言、质量python源码实录编译器、源码运行时、质量工具链和标准库方面都有所改进。源码语言层面上,质量试验特性loopvar在Go 1.中转正,源码for range新增对整型表达式的质量支持。loopvar语义变更对代码的源码后向兼容性影响较大,需要开发者注意go.mod文件中的质量go version升级到go 1..0或更高版本时的语义变更。此外,源码for range支持后面接整型表达式以及函数迭代器的实验特性,也是Go 1.版本的重要变化。
在编译器、运行时与工具链方面,Go 1.版本继续优化PGO,实现更高比例的调用去虚拟化。Go编译器可以更多地运用devirtualize和inline技术,提高程序的性能。运行时使基于类型的垃圾收集的元数据更接近每个堆对象,从而提高CPU性能。工具链方面,go work vendor功能允许将workspace中的依赖放在vendor目录下,go mod init不再尝试导入其他vendor工具的配置文件,同时go test -cover在Go 1.版本之后报告覆盖率为0.0%。
标准库方面,Go 1.版本新增了math/rand/v2包,这是标准库中第一次为某个包建立v2版本。pile -N -l -S main.go就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是画红线的三处,第一步调用runtime.deferprocStack创建defer对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer总共有三种模型,编译一个函数里只会有一种defer模式。在讲defer实现机制时,我们一起看过defer的xmonad源码分析结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:
简单介绍一下上面的字段:
上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。
下面就开始我们的重点吧~。
在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:简单介绍一下上面的字段:上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。写个简单的例子:运行上面的例子你就会发现,即使在主goroutine中调用了runtime.Goexit,其他goroutine是没有任何影响的。所以结构中的pc、sp、goexit三个字段都是小小帝国源码为了修复runtime.Goexit,这三个字段就是为了保证该函数的一定会生效,因为如果在defer中发生panic,那么goexit函数就会被取消,所以才有了这三个字段做保护。看这个例子:
英语好的可以看一看这个: github.com/golang/go/is...,这就是上面的一个例子,这里就不过多解释了,了解就好。
接下来我们再来看一看gopanic方法。
gopanic的代码有点长,我们一点一点来分析:
根据不同的类型判断当前发生panic错误,这里没什么多说的,接着往下看。
上面的代码都是截段,这些部分都是为了判断当前defer是否可以使用开发编码模式,具体怎么操作的就不展开了。
在第三部分进行defer内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover把recoverd = true,具体这个函数的操作留在下面讲,因为runtime.gorecover函数并不包含恢复程序的逻辑,程序的恢复是在gopanic中执行的。先看一下代码:
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... },正常recover是会绕过Goexit的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit也会被recover住,这里是通过从runtime._panic中取出了程序计数器pc和栈指针sp并且调用runtime.recovery函数触发goroutine的调度,调度之前会准备好 sp、pc 以及函数的返回值。
第二部分主要是做panic的recover,这也与上面的流程基本差不多,他是从runtime._defer中取出了程序计数器pc和栈指针sp并调用recovery函数触发Goroutine,跳转到recovery函数是通过runtime.call进行的,我们看一下其源码(src/runtime/asm_amd.s 行):
因为go语言中的runtime环境是有自己的堆栈和goroutine,recovery函数也是在runtime环境执行的,所以要调度到m->g0来执行recovery函数,我们在看一下recovery函数:
在recovery 函数中,利用 g 中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo 重新调度 g , goroutine 继续执行,im 源码 githubrecovery在调度过程中会将函数的返回值设置为1。这个有什么作用呢? 在deferproc函数中找到了答案:
当延迟函数中recover了一个panic时,就会返回1,当 runtime.deferproc 函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。
在这里runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics 打印出全部的 panic 消息以及调用时传入的参数。
这就是这个逻辑流程,累死我了。。。。
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic,要注意这些:这几个是比较典型的,还有很多会发生panic的地方,交给你们自行学习吧~。
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
go源码解析之TCP连接(六)——IO多路复用之事件注册
在探讨go源码解析之TCP连接(六)——IO多路复用之事件注册这一主题时,我们首先需要理解IO多路复用的基本概念及其在go语言中的实现方式。通常,我们通过系统函数如select、poll、epoll等来实现多路复用,尤其是在Linux操作系统下运行的网络应用程序中。对于直接使用C或C++进行网络程序编写的场景,这种方法较为常见。在这些场景下,应用程序可能在循环中执行epoll wait以等待可读事件,之后将读取网络数据的任务分配给一组线程完成。
然而,在go语言中,情况有所不同。go语言有自己的源码转换在线运行时环境,使用的是轻量级的协程而非传统的线程。这意味着在实现TCP服务器时,go语言能够通过将协程与epoll结合起来,有效地实现IO多路复用。这种结合使得go应用程序在处理网络连接时,能够以更高效的方式响应事件,避免阻塞单个协程。
在实现一个TCP server时,我们通常会为每个连接启动一个协程,这些协程负责循环读取连接中的数据并执行业务逻辑。在go语言中,当使用epoll实现IO多路复用时,其流程包括以下几个关键步骤:
1. **初始化epoll**:在go应用程序中,首先需要初始化epoll实例,以便于监控和响应各种事件。
2. **事件注册**:将新连接的socket加入epoll中,这一步骤类似于将文件描述符与epoll实例关联起来,以便在特定事件发生时接收通知。
3. **事件检测与处理**:在应用程序的主循环中,利用epoll wait检测到可读或可写事件后,根据事件类型执行相应的处理逻辑,如读取数据或写入数据,以及后续的业务逻辑处理。
4. **协程调度与唤醒**:当网络数据可读时,epoll会将事件通知到相应的协程。在go中,协程通过被挂起等待网络数据的到来,当数据可读时,epoll通过调用协程的等待函数(如fd.pd.waitRead),将协程从挂起状态唤醒,从而继续执行读取操作或其他业务逻辑。
通过这一系列过程,go语言成功地将协程与epoll结合,实现了高效的IO多路复用。这种方法不仅提高了并发性能,还简化了网络应用程序的实现,使得go语言在构建高性能、高并发的网络服务时具有显著优势。
总结而言,go语言通过巧妙地将协程与内核级别的IO多路复用技术(如epoll)整合在一起,实现了高效、灵活的网络编程模型。这一设计使得go语言在处理并发网络请求时,能够保持高性能和高响应性,是其在现代网络服务开发中脱颖而出的重要原因之一。
Go语言源码阅读分析(3)-- unsafe
Go语言的unsafe包提供了一套打破类型安全限制的操作,但使用时需谨慎,因为它可能导致代码无法移植。包内主要包含unsafe.go文件和一些声明,实际实现和测试用例并未提供。关键内容如下: 1. Pointer类型:可以转换为任何类型的指针,以及Uintptr类型,这种转换允许直接读写内存,风险极高,需谨慎使用。 - 可以将任意类型转换为Pointer类型,但转换后不能长于原类型,且要求内存布局一致。例如,将float转换为uint的函数`Floatbits`。 - Pointer可以转换为uintptr,但这种转换仅用于内存地址的打印,且不能直接从uintptr恢复为Pointer,除非是枚举类型。 2. 偏移指针:用于访问结构体字段或数组元素,需确保指针不会超出原始对象的内存范围。 3. syscall调用:在syscall包中,某些函数需要在同一条语句中进行指针到uintptr的转换,以确保指针指向的对象在调用时仍然有效。 4. reflect包使用:reflect.Value.Pointer和UndafeAddr返回的都是uintptr,应在获取后立即转换为Pointer,避免对象被GC回收。 5. 反射结构体转换:例如StringHeader和SliceHeader的Data字段,仅在指向活动切片或字符串时有效。 总之,unsafe包的使用需遵循特定的规则和限制,不当使用可能导致程序不稳定或移植问题。接下来的计划是研究reflect包。Go的执行原理以及Go的命令
Go的源码文件主要分为三类:命令源码文件、库源码文件和测试源码文件。
命令源码文件是Go程序的入口,被声明为main包,包含main函数。文件被安装后,会根据GOPATH设置存放于当前工作区的bin目录或GOBIN设置的目录。这些文件可以单独运行,使用go run命令直接执行,或通过go build或go install生成可执行文件。命令源码文件不应与其他文件混合在同一个代码包中。
库源码文件不具备命令源码文件的特征,是普通源码文件。文件被安装后,对应的归档文件(.a文件)会被存放在当前工作区的pkg目录下的平台相关目录。库源码文件不能通过go build或go install编译和安装。
测试源码文件以_test.go为后缀,并包含Test或Benchmark函数。Test函数接受*testing.T参数,用于功能测试;Benchmark函数接受*testing.B参数,用于性能测试。
命令方面,Go的最新版本1.提供了个基本命令,如build、get、install、run等。build命令用于编译代码包及其依赖;get命令用于下载远程代码仓库中的代码包;install命令用于编译并安装代码包;run命令用于运行命令源码文件。build和install命令会在指定目录生成可执行文件;run命令只能运行命令源码文件。install命令还负责将编译结果移动到bin目录或GOBIN目录。get命令会将代码包下载到GOPATH中的src目录。clean命令用于清除已编译生成的文件。
fmt命令用来格式化代码文件,通常与gofmt命令结合使用,格式化后的结果会覆盖源代码文件。test命令自动读取_test.go文件,生成并运行测试用的可执行文件。doc命令提供强大的文档功能,可以查看相应package的文档,甚至创建本地版本的golang.org文档。fix命令用于修复老版本代码到新版本,version命令查看当前Go版本,env命令查看Go环境变量,list命令列出当前安装的所有package。
综上所述,Go的源码文件分类清晰,命令提供了全面的编译、下载、安装、测试和文档支持,满足了开发者的需求。
Go 1.要来了,看看都有哪些变化-第2篇
Go官方团队在..发布了Go 1. rc1版本,预计正式发布日期为年2月份。以下是Go 1.的主要更新内容:
安装方法不再提及。
Go 1.在多个方面进行了优化,包括语言、可移植性、工具链、运行时、编译器、汇编器、链接器和核心库。本篇主要介绍Go工具链方面的优化。
Go命令的改进包括:
标准库的package不再在$GOROOT/pkg目录下存储源代码编译后的文件,而是按需编译并缓存在编译缓存中,以减小Go安装包的大小。go test -json的实现更加鲁棒,无需开发者进行任何更改。但是,直接调用Go工具test2json的开发者需要在测试的可执行程序中增加-v=test2json参数。go test -json的修改使得每个测试程序执行开始时增加了一个Action事件,当同时运行多个测试程序时,这些事件的执行顺序与命令行中的package顺序一致。go命令现在支持与CPU架构相关的编译标记参数,如amd.v2,使开发人员能够根据CPU架构进行不同的处理。go子命令支持-C参数,允许在执行命令前改变目录。go build、go test命令不再支持-i参数,这个参数在Go 1.版本中已被弃用。go generate命令接受-skip参数,可以跳过匹配//go:generate指令。go test命令接受-skip参数,可以跳过匹配测试用例。
go build、go install和其他编译相关命令新增了-pgo标记参数,用于辅助开发者进行程序优化。-pgo指定的是profile文件的路径。如果-pgo=auto,则会在main包路径下查找名为default.pgo的文件。-pgo=off可以关闭优化。go build、go install和其他编译相关命令新增了-cover标记参数,用于收集编译出来的可执行程序的代码覆盖率。
go version -m命令现在支持读取和解析更多类型的Go二进制文件。例如,通过go build -buildmode=c-share编译的Windows DLL文件以及没有可执行权限的Linux二进制文件,现在可以被go version -m解析。
Go标准库中使用cgo的package(如`net`、`os/user`和`plugin`)在不同环境中表现不同。在macOS环境中,net和os/user包已经被重写,不再依赖cgo。在Windows环境中,net和os/user没有使用过cgo。在其他操作系统上,如果编译时禁用cgo,则会使用纯Go语言实现。在macOS环境中,race detector已经被重写,不再依赖cgo。
从Go 1.版本开始,Go支持对任何Go程序进行代码覆盖率收集,而不仅仅是单元测试。为了收集代码覆盖率,需要按照官方文档进行操作。Vet检测单元测试中的循环变量嵌套子函数错误使用的场景。Go 1.版本开始通过go vet检测出这类问题。对于`Time.Format`和`time.Parse`,如果代码试图将日期格式转换为yyyy-dd-mm,会给出提示,因为yyyy-dd-mm不符合ISO 日期格式标准。
总结:Go 1.带来了多项优化,旨在提高开发效率、代码质量以及跨平台兼容性。期待未来版本带来更多改进。
go语言是编译型还是解释型
Go语言是编译型语言。
首先,理解编译型和解释型语言的差异是关键。编译型语言会将源代码转换为机器代码,这是一组可以直接由计算机执行的低级指令。这个过程通常发生在程序运行之前,因此编译型语言通常具有较高的执行速度。相反,解释型语言在程序运行时,会逐行读取源代码并将其转换为机器代码执行。由于这个过程在运行时进行,解释型语言的执行速度通常比编译型语言慢。
Go语言被设计为编译型语言。当我们使用Go编译器(如gc)编译Go程序时,它会将Go源代码(.go文件)转换为二进制可执行文件。这个过程通常发生在程序运行之前。这意味着,一旦编译完成,生成的二进制文件可以直接在计算机上运行,无需任何中间的解释或转换过程。
举个例子,如果我们有一个简单的Go程序,如下所示:
go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
使用Go编译器,我们可以将这个源代码文件编译为一个可执行文件。在命令行中,我们可以使用以下命令来完成这个过程:
bash
go build -o hello hello.go
上述命令会生成一个名为“hello”的可执行文件。这个文件是机器代码,可以直接在计算机上运行。当我们运行这个文件时,它会直接输出“Hello, World!”,无需任何中间的解释或转换过程。
总结来说,Go语言是编译型语言,它将源代码预先转换为机器代码,这使得Go程序具有较高的执行速度。
2024-12-24 09:48
2024-12-24 08:56
2024-12-24 08:49
2024-12-24 07:54
2024-12-24 07:48
2024-12-24 07:38