这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
GO协程
GO我最先听到就是因为GO的协程在多并发场景下的处理能力,文中就来看看GO协程的相关概念和原理。
进程、线程、协程
进程:一个运行的代码就是进程,进程分配的资源是计算机分配的控制计算机资源的最大资源。
线程:是程序执行的最小单元,多个进程之间共享了堆,独占栈,PC,等资源。
协程:是一种轻量级的函数。一个线程可以有多个协程。由程序控制,协程交换不用上下文切换。
并发与并行概念
并行:同一时刻有多个执行流,同一段时间有多个执行流
并发:同一时刻只有1个执行流,通过快速切换,同一时间段有多个执行流。
Goroutine
线程属于内核态,比较消耗资源
协程轻量级,用户态,通过go关键字进行调用,线程之间有很多通信方式,协程之间可以通过channel和共享内存进行通信。
使用
使用go关键字,后面跟上函数就可以。
通信机制
- 使用通道的方式进行通信,先进先出的原理,管道中的数据有序的,并发的时候利用这个机制那么协程之间的数据就比较好维护。所以我们选择的都是channel来进行协程之间的通信,很少采用共享内存方式进行通信。
- 使用共享内存的方式进行通信,里面就需要上锁来维护并发一致性。
channel通信,更加快
通过共享内存,这里会互斥锁
通道channel
推荐使用通道进行协程通信。channel像队列一样先进先出,获取数据的函数没有数据会进行阻塞,通过make(chan 元素类型,[缓冲大小])的方式创建一个通道,然后通过chan<-方式像channel中传入数据,
项目演示:
打印1-9数的平方
协程A生产数字
协程B消费数字
使用defer关键字延迟一个函数或者方法的执行。defer语句会在所有函数最后去执行。
package main
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
}
}()
for i := range dest {
println(i)
}
}
func main() {
CalSquare()
}
使用dest,用了缓冲,消费者速度可能比生产者慢,
package main
import (
"sync"
"time"
)
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 addWithLock()
}
time.Sleep(time.Second)
println(x)
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println(x)
}
func main() {
Add()
}
waitGroup
底层通过计数器来标识任务数,调用Wait()进行阻塞,完成一个任务调用Done()方法,计数器减1,到0就不会阻塞。
协程和线程之间的演示项目:
func hello(i int) {
println("hello goroutine :" + fmt.Sprint(i))
}
func helloGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
//推迟执行完函数才进行done
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
总结:
协程的使用没有太难,但是还是有很多不理解的地方,主要在原理部分。