在开发过程中,我们难免要跨线程/协程进行数据的访问与修改,由此便会产生数据安全性问题,那么对于号称高并发,高性能的go语言来说,又会是怎么样的呢?我们接下来将从内置类型开始,一点一点抽丝剥茧,详细探究并发安全在go中的表现。

内置类型

基本类型

基本类型包括 int(8/16/32/64), uint(8/16/32/64), float(32/64), bool, byte, rune, complex(64/128)

基本类型的访问是安全的,但是修改操作并非安全的,我们看一个例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "sync"

var count int64

func add(wg *sync.WaitGroup) {
	for i := 0; i < 1000; i++ {
		count++
	}
	wg.Done()
}

func main() {
	wg := &sync.WaitGroup{}
	wg.Add(100)
	for i := 0; i < 100; i++ {
		go add(wg)
	}
	wg.Wait()
	println(count)
}

以上代码会输出什么呢?如果你回答100000的话,那么恭喜你,你打错了,正确答案是不确定。对于不同的协程,在同一时刻有可能读到相同的count值,那么同时+1将会发生覆盖的情况,加了多次,但是最后的表现为加了一次,很遗憾,这种情况是无法精确预测的,因此对于结果来说,也是无法预测的,会产生意想不到的结果。

对于上面的加法操作来说,代码虽然只有一行,但是实际的操作会有多个步骤,首先读取count值,然后更新count值,最后替换count值。(因为int为基本类型,是拷贝而不知引用)

那么正确的做法呢?在标准库中,内置了sync/atomic的包,顾名思义,atomic就是原子的意思,是最小的不可分割的意思,意味着,我们所有的操作都是

slice

map

chan

用户类型