查阅源码
写代码的时候,有时会纠结于语言提供的一些机制是否符合预期,这个时候就需要百度搜CSDN了。。。哦不,是需要阅读源码了。
golang的源码有一点特殊,以下简单记录下源码查阅方式,附带几个之前查阅源码的经历。
本文源码基于go-1.17.2。
源码位置
最初以为源码都在官方下载的src包(例如:go1.17.2.linux-amd64.tar.gz)里,实际上分了两部分,基础编译器在1.4的源码里,从1.5开始只包含go和汇编了。
Go 1.5 Release Notes
The compiler and runtime are now written entirely in Go (with a little assembler). C is no longer involved in the implementation, and so the C compiler that was once necessary for building the distribution is gone.
官方文档( https://golang.org/doc/install/source )也给出了指引,关于如何从源码编译golang,要先用1.4编译bootstrap toolchain,然后编译最新版,而1.4的这部分代码会持续维护,以支持后续的更新。
Go 1.4 was the last distribution in which the toolchain was written in C.
因此会发现当前新版的源码包里几乎没有c语言的代码,而且很多代码是找不到的。
查阅源码的时候要准备两份,一份1.4,一份当前版本。
源码:unsafe.Sizeof
函数入口:cmd/gc/unsafe.c->unsafenmagic
可以看到代码里实际还是基于type来计算的:
if(strcmp(s->name, "Sizeof") == 0) {
typecheck(&r, Erv);
defaultlit(&r, T);
tr = r->type;
if(tr == T)
goto bad;
dowidth(tr);
v = tr->width;
goto yes;
}
实际功能:cmd/gc/align.c->dowidth
代码里有一个switch case,根据数据类型设置w,记录到t->width:
1. 基本类型是固定width
2. map直接作为指针占1个word
3. array是m*n
4. string是初始化时固定计算的值,sizeof_String = rnd(Array_nel+widthint, widthptr)
5. interface{}固定2个指针,2个word
6. slice和其他struct通过函数widstruct计算,会把所有fields累加起来,同时计算了alignment,fields计算仍然是递归到dowidth函数
package main
import (
"fmt"
"unsafe"
)
type ST struct {
b bool
n int
s string
}
func main() {
// linux64 platform
// unsafe.Sizeof(map)
m := make(map[string]string)
m = map[string]string{}
fmt.Printf("%d\n", unsafe.Sizeof(m)) // 8
m = map[string]string{"a": "a", "b": "bcd", "ccc": "efg"}
fmt.Printf("%d\n", unsafe.Sizeof(m)) // 8
// unsafe.Sizeof(array)
var a [3]int16
fmt.Printf("%d\n", unsafe.Sizeof(a)) // 6
// unsafe.Sizeof(string)
str := "abcdefghijklmn"
fmt.Printf("%d\n", unsafe.Sizeof(str)) // 16
str = ""
fmt.Printf("%d\n", unsafe.Sizeof(str)) // 16
// unsafe.Sizeof(interface{})
i := uint16(0)
fmt.Printf("%d\n", unsafe.Sizeof(interface{}(i))) // 16
// unsafe.Sizeof(slice)
sl := make([]interface{}, 4, 6)
fmt.Printf("%d\n", unsafe.Sizeof(sl)) // 24
sl = []interface{}{uint16(1), "abcdefg", }
fmt.Printf("%d\n", unsafe.Sizeof(sl)) // 24
// unsafe.Sizeof(struct)
st := ST{}
fmt.Printf("%d\n", unsafe.Sizeof(st)) // 32
}
源码:time.AfterFunc
time.AfterFunc提供了一个很简洁的延时调用,传入duration、func即可工作。
然而用到它的时候,一丝隐隐的不安让我不由得思考:
他是怎么做到异步回调的呢?
他在背后自动创建了任务协程吗?
以及进一步的,time.Sleep会直接让线程进入S状态吗?这样岂不是很费线程?
这些问题可以在源码中找到答案。
p管理了timers实现延时调用,其运行独立于协程。
time.AfterFunc并不创建协程,而是创建timer,p在schedule时处理timers,其在协程执行之前。
相关源文件:time/sleep.go、runtime/time.go、runtime/proc.go、runtime/runtime2.go
调用链:AfterFunc - startTimer - addtimer - doaddtimer - append(pp.timers, t)
timer处理:runtime/proc.go->main - Gosched - gosched_m - goschedImpl - schedule - checkTimers - runtimer
time.Sleep也并不是直接把线程阻塞,而是创建了唤醒timer,并出让goroutine的执行权。
// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
if ns <= 0 {
return
}
gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady
t.arg = gp
t.nextwhen = nanotime() + ns
if t.nextwhen < 0 { // check for overflow.
t.nextwhen = maxWhen
}
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}