go常见的坑

1、 for循环中使用短变量声明初始值

案例1:

type Data struct {
        d *int
}

func main() {
        list := make([]Data, 0)
        for i := 0; i < 10; i++ {
                list = append(list, Data{ d: &i})
        }
        for _, item := range list {
                fmt.Printf("%d  ",*item.d)
        }
        fmt.Println()
}

预期输出:

0  1  2  3  4  5  6  7  8  9

实际输出:

10  10  10  10  10  10  10  10  10  10

原因是在Go中,变量声明之后,在同一作用域中,对于同一变量名,后续的声明是不会分配新的变量(即没有新的地址空间),所以这里的 i 的内存地址始终不变。因此每次变量 i 指向的地址值 最终为10。对于该问题,使用一个新的变量存储,然后再想slice中append临时变量的地址地址即可,如下:

for i := 0; i < 10; i++ {
    temp  := i
    list = append(list, Data{ d: &temp})
}

案例2:

for-range中初始化使用短变量

func main() {
        sl := []int{1,2,3,4,5,6,7,8,9}
        list := make([]*int, len(sl))

        for i, v := range sl{
                list[i] = &v
        }

        fmt.Println(list)
}

此处list中元素的地址都是指向0xc00012a000,其原因与上述一致。解决方法也是使用一个临时变量存储即可

2、go并发的坑 -闭包陷阱

案例1:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	wg := sync.WaitGroup{}
	wg.Add(2)
	for i := 0; i < 2; i++ {
		go func() {
			prefix := fmt.Sprintf("%d", i+1)
			for c := 'A'; c <= 'A'; c++ {
				fmt.Printf("%s:%c\n", prefix, c)
				time.Sleep(time.Millisecond)
			}
			wg.Done()
		}()
	}
	fmt.Println("Card")
	wg.Wait()

}

输出如下:

案例2:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	var a [10]int
	for i := 0; i < 10; i++ {
		go func() {
			for {
				a[i]++
				runtime.Gosched()
			}
		}()
	}
	time.Sleep(time.Millisecond)
	fmt.Println("a", a)
}

案例3:

func main() {
        var wg sync.WaitGroup

        sl := []int{1,2,3,4,5,6,7,8,9}

        for i, v := range sl{
                go func() {
                        defer wg.Done()
                        fmt.Println(i, v)

                }()
                wg.Add(1)
        }

        wg.Wait()
}

 实际输出如下:

8 9
8 9
8 9
8 9
8 9
8 9
8 9
8 9
8 9

这里输出的结果,i, v都是一样的,其原理和上面一样,也是由于变量只会在第一次声明时初始化。这里解决办法是直接使用闭包函数传参,将i,v作为参数传递进去(会进行拷贝),从而达到预期效果,改进代码如下:

go func(i, v int) {  // 注意这里的参数变化
    defer wg.Done()
    fmt.Println(i, v)
}(i, v)

3、defer的坑 -闭包陷阱

案例1

package main

import "fmt"

func main() {
	for i := 3; i > 0; i-- {
		defer func() {
			fmt.Print(i, " ")
		}()
	}
}

问题解析:这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。

如果还不理解:

package main
 
import "fmt"
 
func main() {
    for i := 3; i > 1; i-- { // 循环满足条件的是 3 2,
        defer func() { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1
            fmt.Print(i, " ") // 循环2次 结果均为 1
        }()
    }
}//输出 1 1 
posted @ 2023-01-18 11:24  南昌拌粉的成长  阅读(40)  评论(0编辑  收藏  举报