内容简介:[1000 2 3 4 5]In modify(), array values: [10 2 3 4 5]In main(), array values: [10 2 3 4 5]
//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, 2, 3, 4, 5} modify(array) fmt.Println("In main(), array values:", 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, 0, 10) var s = make([]int, 0, 10) // 到此为止, t,s 都分配了底层数组,cap为10,只是t,s指定了len为0,fmt.Print时才没显示出内容来 fmt.Printf("addr:%p \t\tlen:%v content:%v\n", t, len(t), t) // 这里的%p打印的其实是slice底层数组的首地址 fmt.Printf("addr:%p \t\tlen:%v content:%v\n", s, len(s), s) t = append(s, 1, 2, 3, 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, len(t), t) // t的len为4 fmt.Printf("addr:%p \t\tlen:%v content:%v\n", s, len(s), 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, len(t), t) // t的真实地址,明显和你上面打印的不同 fmt.Printf("addr:%p \t\tlen:%v content:%v\n", &s, len(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, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{ Dir: originalWD, Env: env, Files: allFiles, }) 其中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 内联打印并且 能准确分析程序的变量分配位置(escape),
开启逃逸分析日志很简单,只要在编译的时候加上 -gcflags '-m'
,但是我们为了不让编译时自动内连函数,一般会加 -l
参数,最终为 -gcflags '-m -l'
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 打印垃圾回收以及响应时间等
6
package main import "fmt" func main() { ch := make(chan string) go func() { for m := range ch { fmt.Println("processed:",m) } }() ch <- "cmd.1" ch <- "cmd.2" //won't be processed }
cmd.2未必执行是因为,ch接受者已经准备好了后,ch <- "cmd.2"立马返回,主进程结束所以不能执行
管道可以用for ... range遍历,直到close(ch)返回,否则继续阻塞
import "time" type bb struct{ a int b string } func main() { ch := make(chan bb) go func() { for a := range ch{ fmt.Printf("processed:%d\n",a.b) }}() cc:=bb{1,"222"} ch <-cc close(ch) time.Sleep(time.Duration(3)*time.Second)
如果close(ch)是可以通过<-ch监听关闭信号,判断管道关闭用v,ok : <-ch 中的ok判定
如果关闭ch,v的值根据管道的类型返回,整形是0,字符串是空,结构体是空结构体
7.
// InterfaceStructure 定义了一个interface{}的内部结构 type InterfaceStructure struct { pt uintptr // 到值类型的指针 pv uintptr // 到值内容的指针 }
如下接口类型interface包含一个类型一个值,所以当给接口变量赋值时候,即使对方变量是null,接口类型仍然有值,
这个之后接口与nil比较会失败,最好用err去判断
func main() { var data *byte var in interface{} fmt.Println(data, data == nil) // <nil> true fmt.Println(in, in == nil) // <nil> true in = data fmt.Println(in, in == nil) // <nil> false // data 值为 nil,但 in 值不为 nil }
8 如果 map 一个字段的值是 struct 类型,则无法直接更新该 struct 的单个字段
因为map是有cap属性的,也就是会根据元素大小修改内存位置,所以你无法调用里面struct的值,
// 无法直接更新 struct 的字段值 type data struct { name string } func main() { m := map[string]data{ "x": {"Tom"}, } m["x"].name = "Jerry" }
cannot assign to struct field m[“x”].name in map
9. 虽然有使用 sync.Mutex
做写锁,但是 map
是并发读写不安全的。map属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。会报错误信息:“fatal error: concurrent map read and map write”。
报错原因:
写的时候设置了h.flags,获取的时候检查报错
写完会清除标志位
10.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main import ( "fmt" ) type People interface { Speak(string) string } type Stduent struct{} func (stu *Stduent) Speak(think string) (talk string) { if think == "bitch" { talk = "You are a good boy" } else { talk = "hi" } return } func main() { var peo People = Stduent{} think := "bitch" fmt.Println(peo.Speak(think)) } |
考题中的 func (stu *Stduent) Speak(think string) (talk string)
是表示结构类型 *Student
的指针有提供该方法,但该方法并不属于结构类型 Student
的方法。因为struct是值类型。
修改方法:
go var peo People = &Stduent{} go func (stu Stduent) Speak(think string) (talk string) { //... }
同时应该注意结构指针的方法和结构指针方法的区别,receive是不同的
型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
这条规则说的是如果我们用来调用特定接口方法的接口变量是一个指针类型,那么方法的接受者可以是值类型也可以是指针类型.
类型 T 的可调用方法集包含接受者为 T 的所有方法
以上所述就是小编给大家介绍的《golang问题总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
如何不在网上虚度人生
[美] 肯尼思·戈德史密斯 / 刘畅 / 北京联合出版公司 / 2017-9 / 39.80元
我们平时上网多大程度上是浪费时间,多大程度是在学习、关心社会、激发创造力?我们真能彻底断网,逃离社交网络吗? 手机把都市人变成一群电子僵尸,是福是祸? 浏览记录就是我们将来的回忆录吗?文件归档属于一种现代民间艺术? 不自拍、P图、发朋友圈,我还是我吗? 美国知名概念艺术家戈德史密斯认为:上网绝不是浪费时间,而是一种创造性的活动。在本书中他以跨学科角度、散文式语言进行论证,涉及大众传播学、计算......一起来看看 《如何不在网上虚度人生》 这本书的介绍吧!