这是我参与「第五届青训营」笔记创作活动的第4天。
并发VS并行
并发:多线程程序在单核CPU上运行。
并行:多线程程序在多核CPU上运行。
Go可以充分发挥多核优势,高效运行。
【1】Goroutine
线程:内核态,栈MB级别,可包括多个协程。
协程:用户态,栈KB级别,轻量级。
go func(j int) {
hello(j)
}(i)
time.Sleep(time.Second)
快速执行多次打印,不要求顺序。
子协程完成前,要保证主协程不退出。
【2】CSP Communicating Sequential Processes
协程间通信。
提倡通过通信来共享内存,而不是通过共享内存来实现通信。
因为互斥量加锁影响性能。
【3】Channel
通道,是一种引用类型。
无缓冲通道,创建 make(chan 元素类型),例如 make(chan int),也叫同步通道。
有缓冲通道,创建 make(chan 元素类型,缓冲大小),例如 make(chan int,2),生产消费模型。
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
子协程A,依次发送数字0~9。
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
子协程B,依次接收数字,计算并发送平方数。
for i := range dest {
println(i)
}
主协程,依次接收并输出平方数。
dest使用有缓冲通道,用于生产者速度快,消费者速度慢的情况。
【4】并发安全 Lock
5个协程并发执行,各自对变量执行2000次+1操作。
var (
x int64
lock sync.Mutex
)
声明互斥量。
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
加锁,Lock()获取和Unlock()释放临界区资源。
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
}
不加锁。
期望结果为10000,加锁能保证得到期望值,不加锁不能确定。
【5】WaitGroup
阻塞主协程不退出,直到子协程全部完成,实现并发同步。
在sync包下。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
Add(5),计数器+5。
Done(),计数器-1。
Wait(),阻塞直到计数器为0。