美文网首页golang
go并发、内存等最佳实践

go并发、内存等最佳实践

作者: Go语言由浅入深 | 来源:发表于2021-03-26 21:16 被阅读0次

    【译文】原文地址
    本文是原文作者在学习go总结的一些结论分享给新手们供参考学习。

    内存:栈和堆

    • Go运行时会为每个Goroutine创建1个栈。
    • Goroutine每次退出时,Go运行时不会都去清理栈,运行时会标记栈为invalid,这样其他程序和routine可以申明使用该栈。
    • Go运行时能够观察到变量被引用,因此会将其内存转移到堆上,使goroutine继续可以访问这部分内存。这被称为逃逸分析。
    • 栈内存分配规则:向下共享一般还是留在栈;向上共享一般会逃逸到堆上。
      只有编译器知道什么时候特殊情况出现。为了获取准确的信息可以在编译程序时添加上gcflags参数:
    go build -gcflags=”-m -l” program.go
    
    • 什么时候变量会逃逸到堆上呢?
    • 如果函数退出了变量还被引用
    • 当一个变量在栈中占有太大内存
    • 编译的时候,编译器不清楚占有内存大小

    Goroutines

    • 每个程序至少有一个Goroutine:main主协程,在程序启动的时候自动创建并运行。
    • 一个Go协程就是一个并发执行的函数。注意和并行的区别。
    • 协程并不是操作系统线程,更抽象一层。
    • Go遵循fork-join模型来创建和等待goroutines。
    • 防止内存泄漏:通过创建信号在父协程和子协程之间同步,达到对子协程的终止。通常这个信号是一个只读的channel名为done。父协程传递这个channel给子协程,当要终止子协程的时候关闭这个channel即可。
    • 如果一个协程负责创建一个新协程,它也必须负责确保能停止新协程。
    • 为了在协程里更好的处理错误,创建一个结构体来封装可能的结果和错误。返回一个这个结构的channel。

    Sync package

    WaitGroup

    使用这个函数来等待其他一系列的并发操作完成,并且你可以不用关心并发操作的结果或通过其他方式获取结果。

    Mutex和RWMutex

    锁定一部分代码只让一个Goroutine可执行。最佳实践是在锁的下一行增加一个defer语句释放锁。
    RWMutex提供一个更细粒度的控制读写权限。

    channel

    • channel提供信息流管道服务;值可以在channel中传递到下游。
    • 双向和单向可选
    • channel在Go中是阻塞的。意味着当一个channel满了,任何想写入的goroutine必须等待直到有空间;另一方面如果一个channel是空的,任何想从中读取值的goroutine必须等待直到至少有一个值写入。
    • 读取会发送两个值分别是value和ok。如果channel被关闭了会返回channel值类型的默认值和ok是false。使用range读取channel会检查ok值。
    • 拥有channel的Goroutine应该实例化channel,执行写操作或将channel传给其他goroutine,关闭channel;或者暴露给其他协程来处理。
    Select和for-select语句
    • 在select语句中,所有的分支在计算值时是同时的,第一个就绪的被执行。如果有多个选择,go运行时做随机选择。
    • 使用 <- time.after(n * teme.second)退出select,如果没有分支能满足条件的情况。
    • 如果没有分支准备就绪,default分支会被执行。和for循环结合实现多次检查其他选择。
    for {
      select {
        case <-done: 
          
          return
        default:
          // Do non-preemptable work
      }
    }
    

    Pipeline

    当创建流水线时使用生成器函数将输入转化为channel

    func IntGenerator(done <-chan interface{}, integers ...int) <-chan int { 
      intStream := make(chan int) 
      go func() {
        defer close(intStream)
        for _, i := range integers {
         select {
           case <-done:
            return
           case intStream <- I:
         }
        }
      }()
     return intStream
    }
    
    切片申明和初始化
    var arr []string //  申明切片, 值为nil
    arr := make([]string, 3) //申明并初始化值为 [“”,””,””]
    
    接口
    编译的时候检查

    接口的实现是隐式的并在运行时检查。如果没有遵守interface的规范,在运行时会报错。

    接收器很重要

    package main
    import (
    “fmt”
    )
    type Animal interface {
      Speak() string
    }
    type Dog struct {
    }
    func (d Dog) Speak() string {
      return “Woof!”
    }
    type Cat struct {
    }
    func (c *Cat) Speak() string {
      return “Meow!”
    }
    func main() {
      animals := []Animal{Dog{}, Cat{}}
      for _, animal := range animals {
        fmt.Println(animal.Speak())
      }
    }
    // Output
    ./prog.go:26:32: cannot use Cat literal (type Cat) as type Animal in slice literal:
    Cat does not implement Animal (Speak method has pointer receiver)
    
    接口隔离原则

    Go代码原则:接收接口,返回结构体

    相关文章

      网友评论

        本文标题:go并发、内存等最佳实践

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