最近跟新同事做code review,发现组里的小朋友犯了一些几个新手错误,想想自己以前也犯过这些错,分享给大家,希望大家看完以后可不要犯咯。
- 跨goroutine读写不加锁
- 文件打开后不关闭
- 滥用string方法
- channel死锁
- 不用合适的方法做事
- 文件整个读取进内存
跨goroutine读写不加锁
这个是最常见的错误,很多小朋友并没有并发编程的思想,对于同一个内存空间进行同时读写,例如大家可以猜猜这段代码的执行结果:
func main() {
c := make(chan bool)
m := make(map[string]string)
go func() {
m["1"] = "a" // 第一次冲突访问
c <- true
}()
m["2"] = "b" // 第二次冲突访问
<-c
for k, v := range m {
fmt.Println(k, v)
}
}
代码出自Golang官方博客《Data Race
Detector》,结果是,我也不知道!因为这是运行时(runtime)决定的,运气好时,ab都有,要是倒霉时,只有a或者b,这也就是为什么代码在这种地方需要加锁了(sync.Mutex),因为要保证同时有且只有一个goroutine能访问这个map对象。
同时推荐一本好书《七周七并发模型》,里面详细的描述了这种并发方式,锁模式,而Go推荐的CSP模式,再开一篇文章来讲了。
文件打开后不关闭
file, err := os.Open("file.go") // For read access.
if err != nil {
log.Fatal(err)
}
很多小朋友写到这,就继续写业务其他的代码,殊不知在这里埋下了一个祸根,当打开的文件过多时,很有可能触发too many open
files的系统错误。要知道,*nix系统中,一切皆文件,所以当你触发这个错误时,可能不仅仅是文件打开过多,而是整个程序陷入崩溃。那怎么改呢,很简单,Go官方还专门出了一个语句来帮助大家
—-就是defer
file, err := os.Open("file.go") // For read access.
if err != nil {
log.Fatal(err)
}
defer file.Close() //在这里添加defer语句
滥用string方法
如果要你拼接两个字符串,“foo"和"bar”,你会怎么做?常见的做法是:
a, b := "foo", "bar"
c := a + b
既然这个做法自然是错的,可是为什么呢?
因为Go的字符对象是不可变的,意味着每一个新对象都是新的内存空间,当越来越多时,gc一定会不堪重负的。那map对象怎么办?map[[]byte]interface{}这样的结构编译器是不认的,Go官方给出了一个编译器级的优化
m := map[string]string
b := []byte("hello")
m[string(b)] = "world" //注意这个string方法,只能用在这里
/*
这么做一样会分配c的内存空间
c := string(b)
m[c] = "world"
*/
channel死锁
r := make(chan int, 1)
r <- 2
r <- 3 // 代码似乎只执行到这了
<-r
很多新手都在这里问:“go不是号称轻量级纤程么,怎么卡住呢?",而且在Go 1.6
runtime会直接报出panic。那是因为这个channel已经固定数量了,如果channel内的数据不被消耗,那么这个负责输入的goroutine就会一直卡在r<-3那里,直到channel中的2被取走。
小结
前三个错误我工作中都已经碰到过4次以上,绝大多数都是编程新手犯的错误,不仅仅局限于Go,每次都要花半个小时给新人讲,这下可以丢文章给他们了。而剩下的两个我就不讲了,因为用atoi转化unix
timestamp,不是用time库的人并不多……把整个文件读到buffer中,很容易就会发现内存超量了……
希望对大家有帮助,欢迎来@mengzhuo里面的提问题或者吐槽~