Go 1.24 Release Notes

Introduction to Go 1.24 The latest Go release, version 1.24, arrives six months after Go 1.23. Most of its changes are in the implementation of the toolchain, runtime, and libraries. As always, the release maintains the Go 1 promise of compatibility. We expect almost all Go programs to continue to compile and run as before. Changes to the language Go 1.24 now fully supports generic type aliases: a type alias may be parameterized like a defined type. See the language spec for details. For now, the feature can be disabled by setting GOEXPERIMENT=noaliastypeparams; but the aliastypeparams setting will be removed for Go 1.25. ...

二月 14, 2025 · 20 分钟 · anoya

互斥锁与自旋锁

在现实生活中,锁的作用无非是将物品关起来,防止被他人进入等。而在程序中锁的概念也是如此,当同一个内存地址被同时访问时,会造成数据竞争,意思是不知道当前变量被谁所操作,所以引入了锁的概念,当有人来访问时,对此变量加上一把锁来防止其他对象对其访问,这样才会更安全的使用变量做操作。现实中锁的种类有多种,比如物理锁、指纹锁等。在程序中亦是如此,这里大类氛围两种,互斥锁和自旋锁。 1. 数据竞争(Data Race)1 Data Race 又称 Race Condition。它旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机。此词源自于两个信号试着彼此竞争,来影响谁先输出。 计算机为了提高性能,出现了多线程和多进程,增加了执行并行能力,但是在保持数据一致上就需要外力来维持。而造成这一原因的就是数据竞争,多个执行者对一个事物的操作。最为常见的例子是在银行存取款,如果付款存在多个交易时较为容易出现这类问题。 事例 如果你银行卡中有余额 1000 元,当你同时购买两个售价分别是 100 和 200 元的商品时,在银行账户中的操作可能是这样的 1000 -100 = 900 // 购买 100 元商品时扣款 900 - 200 = 700 // 购买成功第一个商品后,扣款 200 元商品 1000 - 200 = 800 // 购买 200 元善品时扣款 余额可能是这三种,因为在同时付款的时候,你不知道哪个扣款是先处理操作。所以会出现上述情况,按理说付款完成后余额剩余 700 才是正确的,但是为什么会出现这种情况,是因为出现了数据竞争,但进行扣款操作时候,两者都拿到了总额为 1000 的进行扣除,其中有一方肯定错误的扣除处理。 # 使用Golang实现 package main import ( "fmt" "sync" ) // 余额 var Balance uint = 1000 // wg var wg sync.WaitGroup // 消费行为 func consume(money uint) uint { defer wg.Done() Balance -= money return Balance } func do() { defer wg.Wait() goods := [2]uint{100, 200} // 同时有两款交易进行中 for i := range goods { wg.Add(1) go func(i int) { fmt.Printf("Current balance is: %v\n", consume(goods[i])) }(i) } } 运行函数do(),得到输出为: ...

一月 24, 2025 · 6 分钟 · anoya

初识Go内存管理

计算机存储设备的读写入读金字塔 我们所说的内存管理是在堆上,栈不需要操作,因为是自动回收的 栈:先进后出,出栈和入栈 内存管理基于 tcmalloc (thread-caching malloc), Page: 大小为 4kb,无论是申请还是释放内存都以 Page 为单位,也就是 4kb Span: 内存块,由多个 Page 构成 SizeClass: Object: 分配流程 当程序需要内存时,会像堆请求分配内存,得到响应后,堆内存会从为分配的内存中切割出一小块内存,用来分配。中间使用链表将各个小块堆内存连接起来 分配对象时,会优先在已分配(释放后)的内存中查找近似的大小块,如果没有合适的,将在未分配的堆内存中切割类似大小的内存用来分配。 当分配的内存多了起来,在每一段分配的内存中会存在未使用的内存,这就是碎片化产生的原因。那么如何将未使用的内存,也就是碎片的内存重新利用起来? 碎片化内存利用 内存对齐 CPU 访问内存时是以字长为单位,减少对内存的访问量,加大 CPU 处理的吞吐量 现在 CPU 普遍是 64 位,字长为 8 字节,32 位的CPU,字长位 4字节 内存对象 当 非内存对齐 TCMalloc Google 开源内存分配 小对象分配 小对象的分配基于 page,当需要分配的对象小于单个 page 时,采用小对象分配方式 page 单个 page 占用为 4kb, 定长对象分配 变长对象分配 内存分配时,“取整”操作,如7字节的占用,就分配8字节的长度。但是分配并不是按照2的幂数, 目前所得时8, 16, 32, 48, 64, 80(等待测试),这样分配的目的是为了减少碎片化,如果按照2的幂数计算,如65字节的占用,将会分配128字节的长度,碎片化占比接近一半。 大对象分批 span span 是一种分配单位, RadixTree ...

一月 24, 2025 · 1 分钟 · anoya

Go 编译原理

在编译型语言中,当使用类似 build 命令进行编译时,在其内部会严格进行内部转换,从编码层转变为机器层(汇编语言),从而使代码赋予的逻辑运行在机器上。但是这一过程对于我们开发者来说是不可见(黑盒)的,接下来从 go build main.go 来解释这其中发生了什么? 引用: AST生成: https://pkg.go.dev/go/ast 编译流程概述: 词法分析 -> 语法分析 -> 类型检查 -> AST 到 SSA 转换 在一个 Go 程序被创建时,使用 go build 指令编译后运行二进制文件的过程中,其中经历了多次转换,最终运行在操作系统之上(在Go中,最终编译为汇编语言) 例如我们最熟悉不过的代码,输出 Hello, world(下文所有分析依据于此片段): package main import "fmt" func main() { fmt.Println("Hello, world") } 无疑是我们最为常见,但从保存文件到运行,中间做了什么操作,无从得知,得到的只有结果,而中间的过程是不会显示的。 在使用 Go自带的 run 命令后,程序从编译态(Go 为静态语言)到运行态的过程,首先经过编译器,而其主要负责以下操作: 构建 AST,此操作为多数语言编译的前提操作,构建抽象语法树。当前阶段主要负责分析、检查和代码生成 加载 Runtime 运行,主要是在 main 函数之前加载代码 在执行 build 或者 run 命令后,Go 编译器是怎样进行处理的? Terminal 中执行命令:GOSSAFUNC=main go build main.go GOSSAFUNC=main 的意思是:在编译过程中将SSA暴露出来。 命令执行后,会在当前目录下生成文件ssa.html 如下图所示 打开文件在浏览器中显示 一、解析 Go 源码 Go 语言的解析过程主要包括词法分析和语法分析两个阶段。在编译过程中,Go源代码首先会被转换成抽象语法树(AST),然后进行类型检查和代码生成等操作。 ...

十一月 22, 2024 · 2 分钟 · anoya