Function as a Method Receiver

通常来说,我们会使用某个具体的对象(struct)来作为receiver实现接口,但有时候,使用函数作为receiver可以起到不一样的效果。 使用函数作为receiver一个最常见的例子是HandleFunc: type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } 这里将HandleFunc作为接收器,实现了ServeHTTP方法,同时也实现了http.Handler接口 type Handler interface { ServeHTTP(ResponseWriter, *Request) } 代码注释中对与HandleFunc的解释如下: // The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. HandleFunc的定位是:适配器。为什么这么说?有了这个适配器,我们就可以这样完成一个http server: func main() { http.HandleFunc("/echo", func(w http.ResponseWriter, r *http....

November 26, 2023 · 1 min · 李昌

runtime篇五:slice

本系列代码基于golang1.19 runtime篇一:接口 runtime篇二:通道 runtime篇三:defer runtime篇四:panic runtime篇五:slice 推荐阅读:slice和数组的区别 切片append规则 1. 切片的结构 切片的底层表示是runtime.slice: type slice struct { array unsafe.Pointer len int cap int } 非常简单,只有一个array指针指向底层数组结构,len标记切片长度,cap标记数组容量。 2. 新建一个切片 当我们使用如make([]int, 0)语法来新建一个切片的时候,调用的是runtime.makeslice函数: func makeslice(et *_type, len, cap int) unsafe.Pointer { mem, overflow := math.MulUintptr(et.size, uintptr(cap)) if overflow || mem > maxAlloc || len < 0 || len > cap { // NOTE: Produce a 'len out of range' error instead of a // 'cap out of range' error when someone does make([]T, bignumber)....

August 14, 2022 · 3 min · 李昌

runtime篇三:defer

本系列代码基于golang1.19 runtime篇一:接口 runtime篇二:通道 runtime篇三:defer runtime篇四:panic runtime篇五:slice 1. defer是什么 defer,是一种特殊的机制,在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,多个defer的执行顺序与声明顺序相反。 对于defer的使用及需要注意的地方,可参考defer用法。这里不再讨论。 在golang runtime中,defer被描述为一个结构体: type _defer struct { started bool // 是否开始执行defer函数 heap bool // 是否分配在堆上 openDefer bool // 是否经过开放编码(open-coded)的优化 sp uintptr // 调用defer时的栈指针 stack pointer pc uintptr // 调用defer函数时的pc值 fn func() // defer关键字传入的函数, 当使用open-coded defer时可为空 _panic *_panic // defer函数中的_panic链表 link *_defer // 在goroutine中的下一个defer,可指向堆或栈 // 如果openDefer为true,则下面的字段将记录具有open-code defer的栈帧和相关的函数。 // 上面的sp将为帧的sp,pc将为defer调用的地址。 fd unsafe....

August 12, 2022 · 5 min · 李昌

runtime篇四:panic

本系列代码基于golang1.19 runtime篇一:接口 runtime篇二:通道 runtime篇三:defer runtime篇四:panic runtime篇五:slice 1. panic的底层结构 panic在runtime中的底层表示是runtime._panic结构体。 type _panic struct { argp unsafe.Pointer // 指向defer调用时参数的指针 arg any // panic参数 link *_panic // 连接到更早的_panic pc uintptr // 程序计数器 sp unsafe.Pointer // 栈指针 recovered bool // 当前panic是否被recover恢复 aborted bool // 当前panic是否被中止 goexit bool // 是否调用了runtime.Goexit } 类似于_defer,panic也被组织成链表结构,多个panic通过link字段连接成一个链表。 在_panic结构体中,pc、sp、goexit三个字段是为了修复runtime.Goexit带来的问题引入的[1]. 2. 调用panic 在函数中调用panic时,底层会调用runtime.gopanic,其源码如下: func gopanic(e any) { gp := getg() // 获取当前g // ....

August 12, 2022 · 7 min · 李昌

runtime篇二:通道

本系列代码基于golang1.19 runtime篇一:接口 runtime篇二:通道 runtime篇三:defer runtime篇四:panic runtime篇五:slice 1. chan的结构 一个channel长这样: type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 // chan中元素大小 closed uint32 // 是否关闭 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters lock mutex } channel的字段中,主要可以分为三部分:...

August 5, 2022 · 7 min · 李昌

runtime篇一:接口

本系列代码基于golang1.19 runtime篇一:接口 runtime篇二:通道 runtime篇三:defer runtime篇四:panic runtime篇五:slice 1. 接口的内部结构 type iface struct { tab *itab data unsafe.Pointer } 一个接口是一个iface结构体,其中包含一个itab指针和一个unsafe.Pointer。 概念上讲一个接口的值,接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。 一个itab可以表示一个接口的类型和赋给这个接口的实体类型,即为接口的动态类型,而data所指向的unsafe.Pointer则指向接口的动态值。 近距离来看itab: type itab struct { inter *interfacetype _type *_type hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. } 其中inter字段描述了接口的类型,_type字段描述了实体类型,hash字段是类型哈希,用于类型匹配,fun字段放置和接口方法对应的具体数据类型的方法地址。 再来看interfacetype type interfacetype struct { typ _type pkgpath name mhdr []imethod } 其中typ和itab中的_type为同一个值,pkgpath则存储了接口的包名,mhdr则表示接口所定义的函数列表。...

August 4, 2022 · 9 min · 李昌

Go汇编之定义基本数据类型

1. Go汇编基础 这里只介绍本文会用到的语法 4个虚拟寄存器 FP: Frame pointer:伪FP寄存器对应函数的栈帧指针,一般用来访问函数的参数和返回值;golang语言中,函数的参数和返回值,函数中的局部变量,函数中调用子函数的参数和返回值都是存储在栈中的,我们把这一段栈内存称为栈帧(frame),伪FP寄存器对应栈帧的底部,但是伪FP只包括函数的参数和返回值这部分内存,其他部分由伪SP寄存器表示;注意golang中函数的返回值也是通过栈帧返回的,这也是golang函数可以有多个返回值的原因; PC: Program counter:指令计数器,用于分支和跳转,它是汇编的IP寄存器的别名; SB: Static base pointer:一般用于声明函数或者全局变量,对应代码区(text)内存段底部;可认为是内存的起源,所以符号foo(SB)就是名称foo作为内存中的一个地址。这种形式被用于命名全局函数和数据,如果将<>添加到名称中,如foo<>(SB),则代表此标识符只在当前源文件中可见。可对名称添加偏移量,如foo+4(SB)指foo开头之后的四个字节。 SP: Stack pointer:指向当前栈帧的局部变量的开始位置,一般用来引用函数的局部变量,这里需要注意汇编中也有一个SP寄存器,它们的区别是:1.伪SP寄存器指向栈帧(不包括函数参数和返回值部分)的底部,真SP寄存器对应栈的顶部;所以伪SP寄存器一般用于寻址函数局部变量,真SP寄存器一般用于调用子函数时,寻址子函数的参数和返回值(后面会有具体示例演示);2.当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真SP寄存器,而a(SP)、b+8(SP)有标识符为前缀表示伪寄存器; 所有用户定义的符号都作为偏移量写入伪寄存器 FP(参数和局部变量)和 SB(全局变量) 常量 Go汇编语言中常量以$美元符号为前缀。常量的类型有整数常量、浮点数常量、字符常量和字符串常量等几种类型。 $1 // 十进制 $0xf4f8fcff // 十六进制 $1.5 // 浮点数 $'a' // 字符 $"abcd" DATA指令 DATA命令用于初始化包变量,DATA命令的语法如下: DATA symbol+offset(SB)/width, value 其中symbol为变量在汇编语言中对应的标识符,offset是符号开始地址的偏移量,width是要初始化内存的宽度大小,value是要初始化的值。其中当前包中Go语言定义的符号symbol,在汇编代码中对应·symbol,其中·中点符号为一个特殊的unicode符号;DATA命令示例如下 DATA ·Id+0(SB)/1,$0x37 DATA ·Id+1(SB)/1,$0x25 这两条指令的含义是将全局变量Id赋值为16进制数0x2537,也就是十进制的9527; 我们也可以合并成一条指令 GLOBL 用于将符号导出,例如将全局变量导出(所谓导出就是把汇编中的全局变量导出到go代码中声明的相同变量上,否则go代码中声明的变量感知不到汇编中变量的值的变化),其语法如下: GLOBL symbol(SB), width 其中symbol对应汇编中符号的名字,width为符号对应内存的大小;GLOBL命令示例如下: GLOBL ·Id, $8这条指令的含义是导出一个全局变量Id,其大小是8字节(byte); 结合DATA和GLOBL指令,我们就可以初始化并导出一个全局变量.例如: GLOBL ·Id, $8 DATA ·Id+0(SB)/8,$0x12345 2....

June 10, 2022 · 2 min · 李昌

一份好用的golang应用Dockerfile模板

# 编译环境FROMgolang:alpine as builder # 设置go环境变量ENV GO111MODULE=on \ GOPROXY=https://goproxy.cn,direct# 工作目录WORKDIR/app# 将项目拷贝到docker中COPY . .# 拉取包,编译RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o hello-app .# 运行环境FROMscratch# 设置时区COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/ShanghaiENV TZ Asia/Shanghai# 安装证书RUN apt-get -qq update \ && apt-get -qq install -y --no-install-recommends ca-certificates curlWORKDIR/app# 将编译好的可执行文件从编译环境中拷贝到运行环境中COPY --from=builder /app/hello-app .# 启动ENTRYPOINT ["./hello-app"]# 端口EXPOSE10000

April 2, 2022 · 1 min · 李昌

切片append规则

大约2021年8月份,go社区对切片容量增长的方式进行了一次调整。具体讨论可见:https://groups.google.com/g/golang-nuts/c/UaVlMQ8Nz3o 1. 之前的增长规则 先看源码 runtime/slice.go func growslice(et *_type, old slice, cap int) slice { // 省略部分条件检查 // ... newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.cap < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed....

March 19, 2022 · 2 min · 李昌

for...range要点

range循环时,使用的是被迭代的元素的副本 type T struct { n int } func main() { ts := [2]T{} for i, t := range ts { switch i { case 0: t.n = 3 // 被访问的是ts的副本 ts[1].n = 9 case 1: fmt.Print(t.n, " ") } } fmt.Print(ts) } 输出:0 {{0} {9}} range 循环语句使用的临时变量 func main() { h := make([]*int, 3) u := []int{1, 2, 3} for i, v := range u { h[i] = &v } for i := range h { fmt....

March 12, 2022 · 1 min · 李昌