这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
Go 语言并发编程
并发与并行
- 并发:多线程程序在一个核的CPU上运行
- 并行:多线程程序在多个核的CPU上运行
- Golang 可以充分发挥多核优势
协程 Goroutine
协程:用户态,轻量级线程,栈 KB 级别,与线程相比,协程不受操作系统的调度,协程的调度器由用户应用程序提供,按照用户调度策略把协程调度到线程中运行。
线程:内核态,从属于进程,栈 MB 级别,每个进程至少包含一个线程,线程是 CPU 调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信方式来通信。
CSP 并发模型(Communicating Sequential Processes)
Golang 实现了 CSP 并发模型做为并发基础,底层使用goroutine做为并发实体,goroutine 非常轻量级可以创建几十万个实体。实体间通过 channel 继续匿名消息传递使之解耦,在语言层面实现了自动调度,大大简化了并发编程的思维转换和管理线程的复杂性。
Channel通道
创建方式:
make(chan 元素类型,[缓冲大小])
// 无缓冲通道
make(chan int)
// 有缓冲通道
make(chan int, 2)
通道是用来传递数据的一个数据结构,可以用于两个 Goroutine 之间,通过传递一个指定类型的值来同步运行和通讯。
- 操作符 <- 用于指定通道的方向,实现发送或接收
- 若未指定方向,则为双向通道
例子
A 子协程发送 0~9 数字, B 子协程计算输入数字的平方, 主协程输出最后的平方数
func CalSquare() {
src := make(chan int)
dest := make (chan int, 3)
// A 协程生产
go func () {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
// B 协程消费
go func() {
defer close(dest)
for i := range src {
dest <- i*i
}
}()
// M 主协程进行复杂操作
for i := range dest {
println(i)
}
}
并发安全 Lock
Mutex 互斥锁
- 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 Goroutine 可以访问共享资源。Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁。
- 使用互斥锁能够保证同一时间有且只有一个 Goroutine 进入临界区,其他的 Goroutine 则在等待锁;
- 当互斥锁释放后,等待的 Goroutine 才可以获取锁进入临界区,多个 Goroutine 同时等待一个锁时,唤醒的策略是随机的。
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
WaitGroup
WaitGroup 是 Go 内置的 sync 包解决任务编排的并发原语。WaitGroup 直译是“等待组”,就是等待一组协程完成任务。如果没有完成,就阻塞。
使用 WaitGroup 的常规套路如下:
- 声明 WaitGroup 变量
- 执行 Add 方法。协程组的个数有 n 个,执行 Add(n)
- 协程组中,每个协程最后,执行方法 Done
- 在协程组后面,执行 Wait 方法
func HelloPrint(i int) {
fmt.Println("Hello WaitGroup :", i)
}
// 首先通过add方法,对计数器+5,然后开启协程,每个协程执行完后,通过 done 对计数器减少 1,最后 wait 主协程阻塞,计数器为 0, 退出主协程。右边是最终的输出结果。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
HelloPrint(j)
}(i)
}
wg.Wait()
}