1、defer总是在函数执行体的最后执行。 如果你在一个for循环中调用defer,然后defer之后还要做一些处理,那么很可能会出问题 defer不会在循环体的最后执行,只会在所有循环的处理完成之后在函数的最后执行。 这种情况下如果你希望每次循环结束做一次defer的处理,那么可以把循环体的内容放进一个函数,在这个函数的内部使用defer语句。
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // NOTE: risky; could run out of file descriptors
// ...process f...
}
上面的代码可以改为:
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// ...process f...
}
2、for i:= range 数组{ go func(){ fmt.println(i)} time.sleep(十秒)} 上面这个简单语句得到的结果可能并非如你所愿,这段简单的代码涉及到几个重要知识点: gorounting的异步性,闭包,传值与传引用。 上面代码其实稍微修改就正确了:
for i:= range [3]int{1,2,3} {
j := i
go func() {
fmt.Println(j)
}()
time.Sleep(10 * time.Second)
}
上面那个错误程序结果大概率是3 3 3,并不会是乱序的1 2 3,你可能会想到了乱序,因为gorouting非同步执行,但是还有一点就是这个匿名gouroutin持有的i是指向同一个地址,这也就是说for range循环结束后会把i的值赋值为最后一次循环的结果,而gorouting大概率会是在for循环结束后调用,这就是原因。
找到原因就好办了,很好修改。 其实就加了一句j := i, 这样就使得go语句调用的匿名函数以闭包的形式持有了j的值,而j的值又是通过 j :=i 对i进行了值复制,而不是持有了i值的引用,这样gorouting在异步情况下执行也不会因为前面for range的同步循环覆盖掉i的值。
3、通道发送和接收的顺序要一致,否则会死锁,尤其channel of channel,也就是双层通道,更要注意。 4、函数返回值问题 函数返回值如果定义为接口,那么可以返回实现这个接口的指针,也就是用取地址符&获得的那个东西,而且你不能直接返回对象实例,必须是对象的地址,否则编译错误。 但是如果函数返回值定义为接口的指针,你就算返回对象指针也不对,类型不匹配,无法通过编译,通常不会这写,有时间好好研究下这个问题。 通常的最佳实践是函数定义返回接口,实现上返回对象指针。 如果函数定义返回的是结构体指针(对象指针),那么实际返回值也必须是对象地址(指针) update: 上面只是现象,研究了下发现了个原因,就是如果一个接口的实现方法关联的是一个结构体地址,然后你的函数返回值要求返回那个接口,这时候你必须返回是实现这个接口的结构体实例的地址,而不能是结构体实例,否则因为你的方法接收者是个指针,你返回的如果不是指针,就不能传给方法接收者,编译器就会报错。编译不会(也不允许)给你取地址传给方法。