【4】Go并发编程 | 青训营笔记

59 阅读2分钟

这是我参与「第五届青训营」笔记创作活动的第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。