+

defer

defer 执行循序为LIFO(后进先出),参数的值在defer语句执行时就已经确定。

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

输出

4 3 2 1 0 

defer 并不是免费的。比如 defer Mutex.Unlock 比直接Mutex.Unlock开销更大

对于一些简短的加锁-修改某个值-释放锁的操作可以这么做。但是如果在加锁之后,可能在不同的分支都需要执行释放锁,还是建议直接使用defer Mutex.Unlock, 否则在(以后)修改或者新增分支的时候可能忘记释放锁从而引入bug。除非必要,应该以代码的可维护性为主

var i int
func unlockDefer(l *sync.Mutex) {
    l.Lock()
    defer l.Unlock()
    i = rand.Int()  
}
func unlockDirect(l *sync.Mutex) {
    l.Lock()
    i = rand.Int()
    l.Unlock()
}
func BenchmarkUnlockDefer(b *testing.B) {
    l := &sync.Mutex{}
    for n := 0; n < b.N; n++ {
        unlockDefer(l)
    }
}
func BenchmarkUnlockDirect(b *testing.B) {
    l := &sync.Mutex{}
    for n := 0; n < b.N; n++ {
        unlockDirect(l)
    }
}

BenchmarkUnlockDefer-4          20000000                56.9 ns/op
BenchmarkUnlockDirect-4         50000000                31.7 ns/op

切片和数组

array的长度是类型的一部分

[3]int[4]int

array 是传值的,值指的是整个数组的内容

func modify(a [3]int) {
    a[0] = 0
}
func main() {
    x := [...]int{3,2,1}
    modify(x)
    fmt.Println(x)
}

输出

[3 2 1]

slice 本质也是传值,但看起来像传递了底层数组的引用

golang 都是传值的,slice看起来像是传引用是因为slice的值本身包含了一个对底层数组的引用(指针)。通过查看下面的slice结构体描述就很清晰。src/runtime/slice.go

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
==
==

slice 与内存模型

append 不是线程安全的

var s []int
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            s = append(s, i)
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println(s)

一种可能的输出:

[0 6 7 9]

a[x]与b[y]是否存在竞态(“竞态”就是在多线程的编程中,你在同一段代码里输入了相同的条件,但是会输出不确定的结果的情况。),与a、b是否为同一slice无关,只与a[x]与b[y]是否指向同一块内存区域有关

译:数组、片和结构类型的结构化变量具有可以单独寻址的元素和字段。每个这样的元素就像一个变量。

也就是说对于slice s而言,我们应该将s[i]看做是一个独立的变量(variable)。例如,对于下面这段代码,虽然a、b是两个完全不同是slice,但是a[1]与b[0]指向了同一块内存区域,等同于同一个变量。如果对a[1]与b[0]的并发访问不做可见性保护(如加锁),那么可能在代码中出现并发bug。(并不一定会触发这个bug,但是存在几率。因为现阶段groutine的加载速度没有main程序快,就和这个demo来说)

func main() {
    a := []int{1,2}
    b := a[1:]
    go func() {
        a[1] = 0
    }()
    fmt.Println(b[0])
}
go run -race ./
2
==================
WARNING: DATA RACE
Write at 0x00c0000a2008 by goroutine 6:
  main.main.func1()
      /Users/vicxiao/workspace/go/test/main.go:11 +0x47
Previous read at 0x00c0000a2008 by main goroutine:
  main.main()
      /Users/vicxiao/workspace/go/test/main.go:13 +0xaa
Goroutine 6 (running) created at:
  main.main()
      /Users/vicxiao/workspace/go/test/main.go:10 +0x98
==================
Found 1 data race(s)
a[1]=0a[0]=0

与[]byte相关的性能问题

slice 引用底层数组而造成内存泄露/GC压力

ioutil.ReadAll

复制数据时使用copy比append性能更优

import (
    "crypto/rand"
    "testing"
)
var (
    src = make([]byte, 512)
    dst = make([]byte, 512)
)
func genSource() {
    rand.Read(src)
}
func BenchmarkCopy(b *testing.B) {
    for n := 0; n < b.N; n++ {
        b.StopTimer()
        genSource()
        b.StartTimer()
        copy(dst, src)
    }
}
func BenchmarkAppend(b *testing.B) {
    for n := 0; n < b.N; n++ {
        b.StopTimer()
        genSource()
        b.StartTimer()
        dst = append(dst, src...)
    }
}

注:

dst作为全局变量是防止编译器优化for-loop

测试时,机器不繁忙

uptime;go version;go test -bench=. ./
 11:56:10 up 294 days, 14:58,  3 users,  load average: 0.58, 0.52, 0.63
go version go1.14.1 linux/amd64
goos: linux
goarch: amd64
pkg: copyvsappend
BenchmarkCopy-40           9808320           116 ns/op
BenchmarkAppend-40          479055          8740 ns/op
PASS
ReadFullioutil.ReadAll
Content-Lengthio.ReadFullioutil.ReadAllReadAllReadFull

for range

如果每个元素比较大,循环时使用range 取值的方式遍历,性能较差

这个比较直观,因为有很多内存拷贝

var X [1 << 15]struct {
   val int
   _   [4096]byte
}
var Result int
func BenchmarkRangeIndex(b *testing.B) {
   var r int
   for n := 0; n < b.N; n++ {
      for i := range X {
         x := &X[i]
         r += x.val
      }
   }
   Result = r
}
func BenchmarkRangeValue(b *testing.B) {
   var r int
   for n := 0; n < b.N; n++ {
      for _, x := range X {
         r += x.val
      }
   }
   Result = r
}
func BenchmarkFor(b *testing.B) {
   var r int
   for n := 0; n < b.N; n++ {
      for i := 0; i < len(X); i++ {
         x := &X[i]
         r += x.val
      }
   }
   Result = r
}
BenchmarkRangeIndex-4              10000            127986 ns/op
BenchmarkRangeValue-4                 50          25939345 ns/op
BenchmarkFor-4                     10000            127817 ns/op

这个有什么好的替代方法吗,比如直接使用for i 遍历数组长度?(从根本解决,要么不要创建大数组,要么大数组不要遍历)

embedding

*T*S

这个主要是防止引入bug,例如下面的并发控制器,错误地在外层将receiver定义为值类型。

type ConcurrentLocker struct{
    sync.Map
}
type LeaveFunc func()
func (cl ConcurrentLocker) Enter(key string) (bool, LeaveFunc) {
    if _, occupied := cl.LoadOrStore(key, struct{}{}); !occupied {
        return true, func(){
            cl.Delete(key)
        }
    }
    return false, nil
}
func main() {
    cl := ConcurrentLocker{}
    key := "some-key"
    fmt.Println(cl.Enter(key))
    fmt.Println(cl.Enter(key))
}

这个错误的代码并不能达到并发控制的效果:

$ go run ./
true 0x10939e0
true 0x10939e0
* sync.MapEnter* ConcurrentLocker