美文网首页Golang学习资料
细数在用golang&beego做api server过

细数在用golang&beego做api server过

作者: tangxqa | 来源:发表于2018-12-15 17:54 被阅读543次

    在介绍之前先说明一下,标题中带有【beego】标签的,是beego框架使用中遇到的坑。如果没有,那就是golang本身的坑。当然,此坑并非人家代码有问题,有几个地方反而是出于性能等各方面的考量有意而为之。但这些地方却是一门语言或框架的初学者大概率会遇到的困惑。

    1、slice/map遍历时的修改问题

    在go中,slice/map的迭代循环的常用方式为:

    slice:
    for index,value := range _slice {
    }
    map:
    for key,value := range _map {
    }
    

    这其中value就是遍历时的当前值。
    按照我们之前在java中的遍历习惯,当遍历到第几个时,拿到的就是指向第几个对象的引用,因此对对象的所有修改行为本质上修改的都是原值。但是在这里并不是。看一个例子:

    //tag1
    var structs = []testModel{
            testModel{
                A: "一",
                B: "一一",
                C: 1,
            },
            testModel{
                A: "二",
                B: "二二",
                C: 2,
            },
            testModel{
                A: "三",
                B: "三三",
                C: 3,
            },
        }
    //tag2
        for _, val := range structs {
            val.A = "四"
            val.B = "四四"
            val.C = 4
        }
    //tag3
        for _, val := range structs {
            fmt.Print(val.A)
            fmt.Print(val.B)
            fmt.Println(val.C)
        }
    

    代码逻辑很简单。

    • 我们在tag1处定义了一个数组,里面包含三个testModel实例。每个testModel实例有三个字段,并且他们的字段值都是互不相同的。
    • 按照事先的想法,是要在tag2处将所有testModel实例的字段值修改为相同的。
    • tag3检查一下修改结果。
      结果console输出如下;
    === RUN   TestLogic
    一一一1
    二二二2
    三三三3
    --- PASS: TestLogic (0.00s)
    PASS
    

    打印的仍然是旧值。那这是为什么呢?
    原因是:在range遍历时,map中的key&value,slice中的index&value,都是新的临时变量,这个临时变量被每一次迭代所共用,临时变量的值也是由被遍历元素复制而来。因此在该变量上修改是无效的。
    那如何才能有效呢? 很简单,对上例的tag2部分做如下修改:

        for key, _ := range structs {
            structs[key].A = "四"
            structs[key].B = "四"
            structs[key].C = 4
        }
    

    同理Slice修改时也需要用slice[index].column = newValue 的方式进行。

    2、map/slice遍历时多协程问题(multi-goroutines)

    在map遍历时,我们有可能会在map中使用go routines进行一些操作,例如下面这个例子:

    var values = []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    
        var block = make(chan int, 2)
    //wrong
        for i, val := range values {
            go func() {
                fmt.Println(1000 + val)
                if i == len(values)-1 {
                    block <- 1
                }
            }()
        }
        for i := 0; i < 2; i++ {
            <-block
        }
    }
    

    我们想用map中迭代的当前值,在协程中做一番大事业,潜意识的写法可能就是按照wrong中的写法那样,直接把value拿过来就用。但是却得到这样的结果:

    === RUN   TestLogic
    1009
    1009
    1009
    1009
    1009
    1009
    1009
    1009
    1009
    --- PASS: TestLogic (0.00s)
    PASS
    

    也就是说,在goroutine中的val,值竟然都是map遍历的最后一个!导致这一现象的原因有两个:

    • for range下的迭代变量val的值是共用的,这一点在《slice/map遍历时修改问题 》中有提到
    • main函数所在的goroutine和后续启动的goroutines存在竞争关系
      为了证实这一点,修改代码为如下:
    for i, val := range values {
            fmt.Println(&val)
            go func() {
                fmt.Println(1000 + val)
                if i == len(values)-1 {
                    block <- 1
                }
            }()
        }
    

    加了一行代码,打印val的内存地址,结果如下:

    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    0xc000287738
    1005
    1009
    1009
    1009
    1009
    1009
    

    val的地址在每次遍历时是同一个!证明第一点;goroutines在不同的遍历中存在变化,例如1005,证明第二点;当然也可用go run -trace ***.go 命令来查看协程的变化,就不多赘述。
    那如何修改呢?只需要使用 函数参数复制 做一次数据复制即可,而不是闭包:

        for i, val := range values {
            go func(val int) {
                fmt.Println(2000 + val)
                if i == len(values)-1 {
                    block <- 1
                }
            }(val)
        }
    

    关于map遍历时多协程并发问题也可参考:https://github.com/golang/go/wiki/CommonMistakes

    3、数组与值拷贝

    首先把结论放在这,然后再展开讨论:
    go语言数组的一切传递都是值拷贝,包括但不限于以下三个方面:

    • 1、数组之间的直接赋值。
    • 2、数组作为函数参数。
    • 3、数组内嵌到struct中。
    数组之间的直接赋值

    看下面一段代码:

        a := []int{1,2,3}
    
        //值复制
        b := a
        fmt.Printf("%p, %v\n", &a, a) //0xc0000bf660, [1 2 3]
        fmt.Printf("%p, %v\n", &b, b) //0xc0000bf680, [1 2 3]
    
        a = append(a, 4)
        a[0] = 4
        fmt.Println(len(b))//3
        for e := range a {//0123
            fmt.Print(e)
        }
    

    首先定义了一个数组a,a中有3个元素。然后通过一次赋值操作,将a赋值给了b。
    在java中,数组之间的赋值,是引用的传递,在a中修改后再通过b进行打印输出,会得到修改后的值。但是刚才说过,go中数组之间的复制操作是值拷贝。因此打印b仍然还是修改前的样子,会发现a和b在内存中是完全不同的两块内存区域。

    数组作为函数参数传递

    因为golang中函数参数的传递都是值拷贝,因此这一点放在数组上也不难理解。
    在如上代码添加一句:test4(a)

        a := []int{1,2,3}
    
        //值复制
        b := a
    
        fmt.Printf("%p, %v\n", &a, a) //0xc0000bf660, [1 2 3]
        fmt.Printf("%p, %v\n", &b, b) //0xc0000bf680, [1 2 3]
    
        a = append(a, 4)
        a[0] = 4
        fmt.Println(len(b))//3
        for e := range a {//0123
            fmt.Print(e)
        }
    
        test4(a)
    

    其中test4代码如下:

    func test4(param []int) {
        fmt.Printf("%p, %v\n", &param, param) //01230xc0000bf700, [4 2 3 4]
    }
    

    会发现a & b & c各有各的内存地址~~

    数组内嵌到struct中
        a := []string{"1", "22"}
        var c = struct {
            S []string
        }{
            S: []string{"1", "22"},
        }
    
        //结构是值拷贝,内部的数组也是值拷贝
        b := c
    
        //修改c中的数组元素值不影响b
        c.S[0] = "2"
    
        //修改b中的数组元素不影响c
        b.S[0] = "3"
    
        //地址不相同,说明每一个变量在内存中是独立的内存区域
        fmt.Printf("%p,%v\n", &a, a)     //0xc0000bd660,[1 22]
        fmt.Printf("%p,%v\n", &b.S, b.S) //0xc0000bd720,[3 22]
        fmt.Printf("%p,%v\n", &c.S, c.S) //0xc0000bd6a0,[3 22]
    

    相关文章

      网友评论

        本文标题:细数在用golang&beego做api server过

        本文链接:https://www.haomeiwen.com/subject/gbnohqtx.html