golang问题总结

前端之家收集整理的这篇文章主要介绍了golang问题总结前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
//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
}

每次调用 都会 new 对象并且把函数指针和堆上(重新)分配的 x 的指针写入,返回对象而不是函数指针
执行的时候将函数对象也传给 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打印垃圾回收以及响应时间等

猜你在找的Go相关文章