同步是在并发编程中的永恒主题,了解 Golang 的小伙伴都知道 Go 里面有一个特殊的应用程序线程叫做 Goroutine,Goroutine 非常轻量且可动态扩容,最小的 Goroutine 只有 2k 大小,所以在使用 Go 语言编程的时候可以开启成千上万的 Groutine,那么 Golang 是如何实现多 Groutine 同步的呢?下面就让我们一起来探索吧。


预备知识

竟态

多个并发进程、线程或协程试图访问相同的内存区域。



结果:

以上情况有三种结果(不考虑实际 Go 运行情况)

什么都不打印,第三行运行在第五行前面

输出为 1,第三行运行在 5,6 之间

输出为 0,第三行运行在 6 之后

在编程的过程中,我们要的是确定性,而不是像上面这种不确定性,不确定性的程序总是会带来一系列问题。

临界区

程序中需独占访问共享资源的部分称为临界区。

如上面的 data++、if data == 0、fmt.Println(data) 分别为三个临界区。


Channel

Channel 分为有缓存和无缓存两种,Channel 的底层实现是一个环形队列,这里只是简单的使用无缓存 Channel,如果有兴趣研究 Channel 实现的朋友可以去看一下 Channel 源码。



结果:

结果为空,保证了先运行 Goroutine,然后运行 main Goroutine


基于信号量实现的一种同步机制。

互斥锁

标准库中 sync 包实现了互斥锁 Mutex,主要方法有 Lock 和 Unlock,分别为加锁和解锁,如果锁定了一个已锁定的互斥锁,那么进行重复操作的 goroutine 将被阻塞,我们来利用这一特性解决一开始的问题。



在第三行将 mu 加锁,如果第 8 行运行在第 5 行之前,那么将会阻塞,等待第 6 行将 mu 解锁,第 8 行再对 mu 加锁,第 12 行解锁,运行完毕。

最终结果一定为什么都不打印。

读写锁

可以看做互斥锁的扩展,标准库 sync 包实现的读写锁 RWMutex,主要有 4 个方法,分别是写操作加锁 Lock 和解锁 Unlock,这对锁和互斥锁一样,读操作加锁 RLock 和解锁 RUnlock,这个与互斥锁有些许区别。

如果临界区加了写锁,那么其它对此临界区加写锁或读锁的地方会阻塞。

如果临界区加了读锁,那么其它对此临界区加写锁的地方会阻塞,加读锁的地方不影响。

其实也比较好理解,就是写写互斥,读写互斥,读读无影响。

死锁

使用多个锁的时候需要注意,有可能会出现死锁的情况。



这将会导致死锁,两个锁都在等着对方解锁,waitGroup 等待着两个 goroutine 的完成,最终 3 个  goroutine 全部堵塞,程序崩溃



条件变量

标准库 sync 包实现了 Cond 结构,这个结构有 3 个方法,我们可以通过使用这三个方法来进行条件下的并发控制,首先来看一下 Cond 结构。



noCopy:保证了这个结构体不可被复制(Go 是值传递),有关 noCopy 内容可以看 noCopy。

Locker:具有 Lock 和 Unlock 方法的接口,也称为锁

notifyList:runtime/sema.go 包的一个有关通知的结构

copyChecker:用来检测结构体是否被 copy

剩下 3 个方法分别是 Wait、Signal、Broadcast,通过名字很容易可以想到,goroutine 需要某个条件才可以继续运行就使用 Wait 方法,然后条件满足的时候使用 Signal 或 Broadcast 分别唤醒单个或多个 goroutine,下面来写一个简单的例子。



结果:

change



结果:

change

change

change


原子操作

原子操作指的是不能再拆分成更小的步骤的操作,换句话讲,原子操作不需要考虑竞态问题,go 标准库 sync/atomic 包提供了相应的原子操作,有 load、store、add、swap、compare 5 中操作。


waitgroup

标准库 sync 包提供的功能,个人感觉是比较好用的一个控制并发的方法,能够保证多个 goroutine 都结束之后某个 goroutine 再结束。使用方法就是初始化一个 waitgroup,然后调用 Add、Wait、Done 3 个方法即可,例子如下。



结果:

3906825714

顺序不应定一样,但是一定是 0-9 这 10 个数


只会执行一次

标准库 sync 包的函数,保证了某个函数只能运行一次,适用于数据库连接池的建立、全局变量的延迟初始化等场景,例子如下。



结果:

1


结语

这篇文章介绍了 Golang 并发控制的一些方法,之后会介绍一些并发模型。