我们都生活在阴沟里,但仍有人仰望星空。——奥斯卡王尔德
1. 前言
这篇文章我们来聊聊在循环中使用Goroutine捕获参数的问题和使用下标获取字符串的字符问题,这两个问题在项目中比较常见,大家记得要规避。
2. Goroutine中捕获参数
goroutine中捕获的循环变量, 都为循环最后的值。
func main() { for i, v := range []string{"a", "b", "c", "d", "e"} { // goroutine中捕获循环变量 go func() { fmt.Printf("index: %v, value: %v\n", i, v) }() } // 此处应该使用waitgroup实现, 为了简单使用了sleep time.Sleep(1 * time.Second) } //================输出============== index: 4, value: e index: 4, value: e index: 4, value: e index: 4, value: e index: 4, value: e
原因:
goroutine中捕获的不是"值", 而是"有地址的变量". for循环可能会先结束, 之后各个goroutine才开始执行. 因此得到的是变量的最终值。
避免方式在goroutine启动的函数中, 把变量作为参数捕获。
func main() { for i, v := range []string{"a", "b", "c", "d", "e"} { // 把循环变量作为参数传入 go func(i int, v string) { // i, v是函数内部的局部变量 fmt.Printf("index: %v, value: %v\n", i, v) }(i, v) } time.Sleep(1 * time.Second) } //================输出============== index: 0, value: a index: 1, value: b index: 4, value: e index: 3, value: d index: 2, value: c
3. 获取字符串的字符
使用下标获取字符串的字符时, 可能得到奇怪的字符
func main() { s := "hello" fmt.Printf("%c\n", s[1]) s = "你好" fmt.Printf("%c\n", s[1]) } //============输出=========== e ½
原因:
golang是以utf8格式保存字符串的, 字符串的下标操作, 访问的是字节, 而不是字符. len函数输出的也是字节数, 如len("hello")==5, len("你好")==6。
避免方式把字符串转化为[]rune/[]int32, 或者使用range遍历
func main() { s := "你好" // 强转为[]rune fmt.Printf("%c\n", []rune(s)[1]) fmt.Println() // 使用range遍历 for _, c := range s { fmt.Printf("%c\n", c) } } //===============输出================= 好 你 好
4. 小结
针对循环创建goroutine获取外面参数这种闭包问题记得一定要小心,根据实际情况做出调整。当然循环根据下标获取字符串字符也会有问题,一定小心谨慎对待。
5. 关注公众号
微信公众号:堆栈future