//author: ysqi,https://yushuangqi.com package main import ( "fmt" ) func sliceModify(slice []int) { // slice[0] = 88 slice[0] = 1000 slice = append(slice,6) } func modify(array []int) { array[0] = 10 fmt.Println("In modify(),array values:",array) } func main() { slice := []int{1,2,3,4,5} sliceModify(slice) fmt.Println(slice) array := []int{1,5} modify(array) fmt.Println("In main(),array) }
[1000 2 3 4 5]
In modify(),array values: [10 2 3 4 5]
In main(),array values: [10 2 3 4 5]
go的函数传递除了map,slice,channel都是值类型传递,特别是数组不是和c一样是引用传递
其次slice的append操作如果超过了cap容量,就会生成新的slice
package main import ( "fmt" "reflect" "unsafe" ) func main() { var t = make([]int,10) var s = make([]int,10) // 到此为止,t,s 都分配了底层数组,cap为10,只是t,s指定了len为0,fmt.Print时才没显示出内容来 fmt.Printf("addr:%p \t\tlen:%v content:%v\n",len(t),t) // 这里的%p打印的其实是slice底层数组的首地址 fmt.Printf("addr:%p \t\tlen:%v content:%v\n",s,len(s),s) t = append(s,1,4) // s的底层数组变化,append返回新的描述struct,假设是tmp,tmp的len则为4,并指向了s的底层数组,再用tmp覆盖t,所以下面两行fmt.Printf打印的%p一样 fmt.Println(t) fmt.Println(s) fmt.Printf("addr:%p \t\tlen:%v content:%v\n",t) // t的len为4 fmt.Printf("addr:%p \t\tlen:%v content:%v\n",s) // s的len为0,因为t,s的len不一样,内容才不同 fmt.Println("---- 辅助代码 -----") sliceHeaderT := (*reflect.SliceHeader)((unsafe.Pointer(&t))) sliceHeaderS := (*reflect.SliceHeader)((unsafe.Pointer(&s))) fmt.Printf("sliceHeaderT: %+v\n",sliceHeaderT) // Data字段的值其实和上面你打印的地址是同一个,自己可以去换算一下 fmt.Printf("sliceHeaderS: %+v\n",sliceHeaderS) fmt.Printf("addr:%p \t\tlen:%v content:%v\n",&t,t) // t的真实地址,明显和你上面打印的不同 fmt.Printf("addr:%p \t\tlen:%v content:%v\n",&s,s) s = append(s,5) // 修改s的底层数组,且len变为1 fmt.Println(t) // 因为t,s 共享底层数组,所以t,s的首个元素都是5 fmt.Println(s) fmt.Printf("sliceHeaderT: %+v\n",sliceHeaderT) fmt.Printf("sliceHeaderS: %+v\n",sliceHeaderS) sliceHeaderS.Len = 2 fmt.Println(s) }addr:0xc420064000 len:0 content:[]
addr:0xc420064050 len:0 content:[]
[1 2 3 4]
[]
addr:0xc420064050 len:4 content:[1 2 3 4]
addr:0xc420064050 len:0 content:[]
---- 辅助代码 -----
sliceHeaderT: &{Data:842350870608 Len:4 Cap:10}
sliceHeaderS: &{Data:842350870608 Len:0 Cap:10}
addr:0xc42000a060 len:4 content:[1 2 3 4]
addr:0xc42000a080 len:0 content:[]
[5 2 3 4]
[5]
sliceHeaderT: &{Data:842350870608 Len:4 Cap:10}
sliceHeaderS: &{Data:842350870608 Len:1 Cap:10}
[5 2]
##############################################################################################
2 golang的闭包和匿名函数
func f(i int) func() int { return func() int { i++ return i } }
和js的闭包如出一辙,但是js的闭包原理没有研究过,golang的源码比较容易分析,但是应该是大差不差
首先要引用局部变量,该变量一定不能在堆栈上,必须分配到堆上才不至于释放,所以,
go 会生成对应的函数对象类型,大概这样
type foo struct {
fp uintptr
x *int
}
执行的时候将函数对象也传给 fp 指向的函数 (比如通过寄存器)
3 golang多进程
1 golang没有如同c函数的fork函数,其多进程实现方案一般有两种, cmd := exec.Command(os.Args[0],args...) process,err := os.StartProcess(argv0,os.Args,&os.ProcAttr{ Dir: originalWD,Env: env,Files: allFiles,}) 其中看过平滑启动的一些代码注意到fork+execv方式派生子进程方式 又重新看了下Nginx源码,Nginx的平滑启动也采用该方式 c代码的fork+exevc派生的子进程会继承父进程打开的socket句柄 但是golang派生子进程需要通过传递属性才会继承所以有了下面代码 env = append(env,fmt.Sprintf("%s%d",envCountKeyPrefix,len(listeners))) allFiles := append([]*os.File{os.Stdin,os.Stdout,os.Stderr},files...) process,}) 其中files是打开的文件句柄 golang默认不传递,但是c的镜像进程方式会传递, 这里面牵扯一个问题, close_on_exec 是一个进程所有文件描述符(文件句柄)的位图标志,每个比特位代表一个打开的文件描述符,用于确定在调用系统调用execve()时需要关闭的文件句柄(参见include/fcntl.h)。当一个程序使用fork()函数创建了一个子进程时, 通常会在该子进程中调用execve()函数加载执行另一个新程序。此时子进程将完全被新程序替换掉,并在子进程中开始执行新程序。 若一个文件描述符在close_on_exec中的对应比特位被设置,那么在执行execve()时该描述符将被关闭,否则该描述符将始终处于打开状态。 所以默认excev会保持打开,如果用此标志位会默认关闭 通过以上发现,真的是环环相扣有点意思5
go test -v -bench . -benchmem
go test -race 竞争检测go build -gcflags=-m -o test 内联打印
go tool objdump -s "main.main" test 汇编打印
go tool pprof MysqL.test cpu.prof 火焰图
/usr/local/Cellar/go/1.8.1/libexec/bin/go build -o wine -gcflags "-N -l -m" && GODEBUG="gctrace=1,scheddetail=1,schedtrace=1000" ./wine打印垃圾回收以及响应时间等