程序员社区

go并发、内存等最佳实践

go并发、内存等最佳实践插图

【译文】原文地址
本文是原文作者在学习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代码原则:接收接口,返回结构体

赞(0) 打赏
未经允许不得转载:IDEA激活码 » go并发、内存等最佳实践

一个分享Java & Python知识的社区