前言
本文从编程的视角来了解Go,包括并发、并行、协程、CSP模型
、Channel
、Mutex
、WaitGrou
等
并发VS并行
并发:多线程程序在一个核的cpu上运行
并行:多线程程序在多个核的cpu上运行
Go语言可以充分发挥多核优势,高效运行
协程Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈MB级别
一个线程里可以同时执行多个协程,Go
可以同时创建上万级别的协程,也是Go
支持高并发原因之一。
代码示例
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
// 开启协程
go hello(i)
}
// 等协程执行结束后,主线程再结束
time.Sleep(time.Second)
}
CSP(Communicating Sequential Processes)
go
语言的最大两个亮点,一个是goroutine
,一个就是chan
。二者合体的典型应用,基本就是大家认可的并行开发神器,简化了并行程序的开发难度,CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型。
Go语言的CSP模型是由协程Goroutine与通道Channel实现:
- Go协程goroutine: 是一种轻量线程,它不是操作系统的线程,而是将一个操作系统线程分段使用,通过调度器实现协作式调度。是一种绿色线程,微线程,它与Coroutine协程也有区别,能够在发现堵塞后启动新的微线程。
- 通道channel: 类似Unix的Pipe,用于协程之间通讯和同步。协程之间虽然解耦,但是它们和Channel有着耦合。
提倡通过通信共享内存,不提倡通过共享内存实现通信
channel
Channel 分为两种:带缓冲、不带缓冲。对不带缓冲的 channel 进行的操作实际上可以看作 “同步模式”,带缓冲的则称为 “异步模式”。
同步模式
发送方和接收方要同步就绪,只有在两者都 ready 的情况下,数据才能在两者间传输(后面会看到,实际上就是内存拷贝)。否则,任意一方先行进行发送或接收操作,都会被挂起,等待另一方的出现才能被唤醒。
异步模式
在缓冲槽可用的情况下(有剩余容量),发送和接收操作都可以顺利进行。否则,操作的一方(如写入)同样会被挂起,直到出现相反操作(如接收)才会被唤醒。
同步模式下,必须要使发送方和接收方配对,操作才会成功,否则会被阻塞;异步模式下,缓冲槽要有剩余容量,操作才会成功,否则也会被阻塞。
语法格式
make(chan元素类型,【缓冲大小】)
- 有缓冲通道 make(chan int)
- 无缓冲通道 make(chan int,2)
代码示例
A子协程发送0 ~ 9数字,B子协程计算输入数字的平方,主协程输出最后的平方数
并发安全LOCK
Mutex-互斥锁
使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。Go语言中使用sync
包的Mutex类型来实现互斥锁。
Mutex
的实现主要借助了 CAS 指令 + 自旋 + 信号量
Rwmutex-读写锁
对于读多写少的场景下使用读写锁性能会比互斥锁好。
写锁优先级高,写锁独占,读锁共享。
读写锁分为两种:读锁和写锁。当一个goroutine
获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine
获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
type RWMutex struct {
w Mutex //复用互斥锁提供的能力
writerSem uint32 //writer信号量
readerSem uint32 //reader信号量
readerCount int32 //存储了当前正在执行的读操作数量
readerWait int32 // 表示写操作阻塞时,等待读操作完成的个数
}
WaitGroup
go 里面的 WaitGroup
是非常常见的一种并发控制方式,它可以让我们的代码等待一组 goroutine 的结束。 比如在主协程中等待几个子协程去做一些耗时的操作,如发起几个 HTTP 请求,然后等待它们的结果。
sync.WaitGroup
可以等待一组 Goroutine 的返回