golang slice 并发写入

​ 由于 slice/map 是引用类型,golang 函数是传值调用,所用参数副本依然是原来的 slice, 并发访问同一个资源会导致竟态条件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
    "fmt"
    "sync"
)

func main() {
    var (
        slc = []int{}
        n   = 10
        wg  sync.WaitGroup
    )

    wg.Add(n)
    for i := 0; i < n; i++ {
        go func() {
            slc = append(slc, i)
            wg.Done()
        }()
    }
    wg.Wait()

    fmt.Println("len:", len(slc))
        fmt.Println("done")
}

(每次执行都会有不同的结果)
out:
len: 3
done

加锁来解决并发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"fmt"
	"sync"
)

func main() {
	var (
		slc []int
		n   = 10
		wg  sync.WaitGroup
	)

	mutex := sync.Mutex{}

	wg.Add(n)
	for i := 0; i < n; i++ {
		go func() {
			mutex.Lock()
			slc = append(slc, i)
			mutex.Unlock()
			wg.Done()
		}()
	}
	wg.Wait()

	fmt.Println("len:", len(slc))
	fmt.Println("done")
}

Chan 来解决并发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
	"fmt"
	"sync"
)
func main(){
	var (
		slc []int
		n   = 100
		wg  sync.WaitGroup
	)

	ch := make(chan int)
	doneCh := make(chan struct{})
	go func() {
		for v := range ch {
			slc = append(slc, v)
		}
		doneCh <- struct{}{}
	}()

	wg.Add(n)
	for i := 0; i < n; i++ {
		tmp := i
		go func() {
			ch <- tmp
			wg.Done()
		}()
	}

	wg.Wait()
	close(ch)
	<-doneCh

	fmt.Println("len:", len(slc))
	fmt.Println("done")

}

基准测试

留个疑点:这里无锁方案反而性能比不过有锁的。

goos操作系统,这里是windows
goarchCPU架构,这里是64位X86
pkgpackage名,可以在测试的时候指定package
cpuCPU的信息,这里可以看到是12代酷睿i5
  1. ns/op 代表每次执行逻辑消耗的时间
  2. B/op 代表每次执行逻辑消耗的内存
  3. allocs/op代表每次执行逻辑申请内存的次数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import "testing"

func BenchmarkLock20(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Lock()
		}
	})
}

func BenchmarkNoLock20(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			noLock()
		}
	})

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$  go test -count=3 -benchtime=5s -benchmem -bench .
goos: windows
goarch: amd64
pkg: xxxx
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkLock20-16                462022(执行次数)             	12215 ns/op            6915 B/op        111 allocs/op
BenchmarkLock20-16                477160(执行次数)              12997 ns/op            6909 B/op        111 allocs/op
BenchmarkLock20-16                335850(执行次数)              16865 ns/op            6908 B/op        111 allocs/op
BenchmarkNoLock20-16              354907(执行次数)              18361 ns/op            5585 B/op        113 allocs/op
BenchmarkNoLock20-16              334875(执行次数)              18441 ns/op            5587 B/op        113 allocs/op
BenchmarkNoLock20-16              317461(执行次数)              18732 ns/op            5591 B/op        113 allocs/op
PASS
ok      xxx 37.195s