zoukankan      html  css  js  c++  java
  • golang中容易遇到的错误

    前言

    在循环中,有几种情况可能会导致混乱,需要弄清楚。

    循环迭代器变量中使用引用

    出于效率考虑,我们经常使用单个变量来循环迭代器。但在循环中,每次循环迭代中都会有不同的值,有时候会导致未知的行为。

    in := []int{1, 2, 3}
    
    var out []*int
    for  _, v := range in {
     out = append(out, &v)
    }
    
    fmt.Println("Values:", *out[0], *out[1], *out[2])
    fmt.Println("Addresses:", out[0], out[1], out[2])
    

    输出

    Values: 3 3 3
    Addresses: 0xc000014188 0xc000014188 0xc000014188
    

    在out这个slice中的元素都是3。

    原因:在每次迭代中,v是单个变量(内存地址不变),所以每次迭代都采用新值将 v append 到 out切片中。

    最简单的解决方法是将循环迭代器变量赋值到新变量中:

    in := []int{1, 2, 3}
    
    var out []*int
    for  _, v := range in {
     v := v
     out = append(out, &v)
    }
    
    fmt.Println("Values:", *out[0], *out[1], *out[2])
    fmt.Println("Addresses:", out[0], out[1], out[2])
    

    新的输出:

    Values: 1 2 3
    Addresses: 0xc0000b6010 0xc0000b6018 0xc0000b6020
    

    在 goroutine 中使用循环迭代变量也会有相同的问题。

    list := []int{1, 2, 3}
    
    for _, v := range list {
     go func() {
      fmt.Printf("%d ", v)
     }()
    }
    

    输出将是:

    3 3 3
    

    请注意,如果不使用 goroutine 运行该函数,则代码将按预期运行。

    循环内使用 defer

    defer 的执行时机是在函数返回前,所以一般不应该在循环内部使用defer

    看一段代码:

    var mutex sync.Mutex
    type Person struct{
    	Age int
    }
    
    persons := make([]Person, 10)
    for _, p := range persons {
        func(){
            mutex.Lock()
            defer mutex.Unlock()
            p.Age = 13
        }()
    }
    

    在上面的示例中,如果使用 defer,则下一次迭代将无法获得互斥锁,因为该锁并没有释放,所以循环会永远阻塞。

    通过委托给另外一个函数的方式,可以使defer提前执行。

    var mutex sync.Mutex
    type Person struct {
     	Age int   
    }
    
    persons := make([]Persons, 10)
    for _, p := range persons {
        func(){
            mutex.Lock()
            defer mutex.Unlock()
            p.Age = 13
        }()   
    }
    

    这种方式特别适合多线程处理数据,导致内存使用率过大,defer用于提前释放内存

    优先考虑使用接口

    接口可以使代码更灵活。这是在代码中引入多态的一种方法。接口允许你定义一组行为而不是特定类型。不使用接口可能不会导致任何错误,但是会导致代码简单性,灵活性和扩展性降低。
    在 Go 接口中,io.Readerio.Writer 可能是使用最多的。

    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    这些接口非常强大,假设你要将对象写入文件,你可以定义了一个 Save 方法:

    func (o *obj) Save(file os.File) error
    

    如果第二天,你想写入 http.ResponseWriter,显然不太适合再创建另外一个 Save 方法,这时应该用 io.Writer

    func (o *obj) Save(w io.Writer) error
    

    另外,你应该知道的重要注意事项是,始终关注行为。在上面的示例中,虽然 io.ReadWriteCloser 也可以使用,但你只需要 Write 方法。接口越大,抽象性越弱。在 Go 中,通常提倡小接口。
    所以,我们应该优先考虑使用接口,而不是具体类型。

    结构体字段顺序

    这个问题不会导致程序错误,但是可能会占用更多内存。

    看一个例子:

    type BadOrderedPerson struct {
     Veteran bool   // 1 byte
     Name    string // 16 byte
     Age     int32  // 4 byte
    }
    
    type OrderedPerson struct {
     Name    string
     Age     int32
     Veteran bool
    }
    

    看起来这两个类型都占用的空间都是 21字节,但是结果却不是这样。我们使用 GOARCH=amd64 编译代码,发现 BadOrderedPerson 类型占用 32 个字节,而 OrderedPerson 类型只占用 24 个字节。为什么?原因是数据结构对齐。在 64 位体系结构中,内存分配连续的 8 字节数据。需要添加的填充可以通过以下方式计算:

    padding = (align - (offset mod align)) mod align
    aligned = offset + padding
            = offset + ((align - (offset mod align)) mod align)
    type BadOrderedPerson struct {
     Veteran bool     // 1 byte
     _       [7]byte  // 7 byte: padding for alignment
     Name    string   // 16 byte
     Age     int32    // 4 byte
     _       struct{} // to prevent unkeyed literals
     // zero sized values, like struct{} and [0]byte occurring at 
     // the end of a structure are assumed to have a size of one byte.
     // so padding also will be addedd here as well.
     
    }
    
    type OrderedPerson struct {
     Name    string
     Age     int32
     Veteran bool
     _       struct{} 
    }
    

    当你使用大型常用类型时,可能会导致性能问题。但是不用担心,你不必手动处理所有结构。这工具可以轻松的解决此类问题:https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment

    race 探测器

    数据争用会导致莫名的故障,通常是在代码已部署到线上很久之后才出现。因此,它们是并发系统中最常见且最难调试的错误类型。

    Go 1.1 引入了内置的数据争用检测器(race detector)。可以简单地添加 -race flag 来使用。

    $ go test -race pkg    # to test the package
    $ go run -race pkg.go  # to run the source file
    $ go build -race       # to build the package
    $ go install -race pkg # to install the package
    

    启用数据争用检测器后,编译器将记录在代码中何时以及如何访问内存,而 runtime 监控对共享变量的非同步访问。

    找到数据竞争后,竞争检测器将打印一份报告,其中包含用于冲突访问的堆栈跟踪。这是一个例子:

    WARNING: DATA RACE
    Read by goroutine 185:
      net.(*pollServer).AddFD()
          src/net/fd_unix.go:89 +0x398
      net.(*pollServer).WaitWrite()
          src/net/fd_unix.go:247 +0x45
      net.(*netFD).Write()
          src/net/fd_unix.go:540 +0x4d4
      net.(*conn).Write()
          src/net/net.go:129 +0x101
      net.func·060()
          src/net/timeout_test.go:603 +0xaf
    
    Previous write by goroutine 184:
      net.setWriteDeadline()
          src/net/sockopt_posix.go:135 +0xdf
      net.setDeadline()
          src/net/sockopt_posix.go:144 +0x9c
      net.(*conn).SetDeadline()
          src/net/net.go:161 +0xe3
      net.func·061()
          src/net/timeout_test.go:616 +0x3ed
    
    Goroutine 185 (running) created at:
      net.func·061()
          src/net/timeout_test.go:609 +0x288
    
    Goroutine 184 (running) created at:
      net.TestProlongTimeout()
          src/net/timeout_test.go:618 +0x298
      testing.tRunner()
          src/testing/testing.go:301 +0xe8
    
  • 相关阅读:
    WCF异常管理—不要在using语句中调用WCF服务(z)
    正则表达式
    nginx汇总(z)
    WCF编解码实现
    zTree -- jQuery 树插件
    C# 程序异常管理方案
    WPF剪切板问题-OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))
    乘法逆元
    二叉树后序遍历(非递归)
    P1892 [BOI2003]团伙
  • 原文地址:https://www.cnblogs.com/niuben/p/15654349.html
Copyright © 2011-2022 走看看