前言
最近决定研究下 Golang,借助王垠大神的说法就是学习一门语言优先从他的特性入手,我们的 Golang 的特性就是 goroutine。这个 goroutine 是一个什么东西呢?就像是漩涡鸣人的影分身。
Goroutine 最简单的例子
我们要在 Golang 中使用协程最简单的方法就是在对应函数前面使用 go
关键字。
1 | package main |
2 | |
3 | import ( |
4 | "fmt" |
5 | ) |
6 | |
7 | func hello() { |
8 | fmt.Println("Hello Goroutine") |
9 | } |
10 | |
11 | func main() { |
12 | go hello() |
13 | } |
我们直接执行就会发现没有任何执行结果,这是为嘛吖?小朋友你是否有很多问号。因为我们的 main
也是一个 goroutine,他是主 goroutine,因为主没有任何的阻塞,主 goroutine(主体鸣人受伤) 快速执行退出,导致其他的 goroutine(影分身) 没有被执行就退出了。
我们来试试简单的修改,让其他 goroutine 也能返回执行出结果。
1 | package main |
2 | |
3 | import ( |
4 | "fmt" |
5 | "time" |
6 | ) |
7 | |
8 | func hello() { |
9 | fmt.Println("Hello Goroutine") |
10 | } |
11 | |
12 | func main() { |
13 | go hello() |
14 | time.Sleep(2 * time.Second) |
15 | } |
这样的问题就是,我们不知道我们的 goroutine 什么时候才会结束啊。不可能无限制延长 time.Sleep
的时间吧,这样不科学。
同步 Goroutine
我们同步 Goroutine 的方式:
- Channel
- sync 包
Channel
我们可以使用 Channel 在通道之间传递值,当然因为 channel 有会阻塞的特性,可以利用这特性来进行协程同步。
1 | package main |
2 | |
3 | import "fmt" |
4 | |
5 | func hello(i chan int) { |
6 | fmt.Println("Hello Goroutine") |
7 | <-i |
8 | } |
9 | |
10 | func main() { |
11 | // 无缓存channel |
12 | // 根据Go语言内存模型规范,对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前 |
13 | i := make(chan int) |
14 | go hello(i) |
15 | i <- 1 |
16 | } |
这个是无缓存 channel 的,在 goroutine 中,channel 先执行了打印语句,然后阻塞等待接收值,知道主 goroutine 告诉他,值来了(你歇着吧)。整个程序流程才会结束。
当然在有缓存的 channel 中这种顺序需要调换一下,否则你会发现程序啥也不输出。
1 | package main |
2 | |
3 | import "fmt" |
4 | |
5 | func hello(i chan int) { |
6 | fmt.Println("Hello Goroutine") |
7 | i <- 1 |
8 | } |
9 | |
10 | func main() { |
11 | // 有缓存channel |
12 | // 对于带缓冲的Channel,对于Channel的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是Channel的缓存大小。 |
13 | i := make(chan int, 1) |
14 | go hello(i) |
15 | <-i |
16 | } |
在我们的接收之前,必须完成发送,否则会发生死锁阻塞。
sync 包
我们也可以使用 sync 包提供的功能进行 goroutine 的同步。
1 | package main |
2 | |
3 | import ( |
4 | "fmt" |
5 | "sync" |
6 | ) |
7 | |
8 | func hello(wg *sync.WaitGroup) { |
9 | fmt.Println("Hello World") |
10 | wg.Done() |
11 | } |
12 | |
13 | func main() { |
14 | var wg sync.WaitGroup |
15 | wg.Add(1) |
16 | go hello(&wg) |
17 | |
18 | // 等待结束 |
19 | wg.Wait() |
20 | } |
利用缓存 channel+sync 包实现对 goroutine 并发控制
1 | package main |
2 | |
3 | import ( |
4 | "fmt" |
5 | "sync" |
6 | "time" |
7 | ) |
8 | |
9 | func main() { |
10 | |
11 | wg := &sync.WaitGroup{} |
12 | |
13 | limiter := make(chan bool, 10) |
14 | for i := 0; i < 100; i++ { |
15 | wg.Add(1) |
16 | limiter <- true // 当传入 10 后,如果没有接收通道会阻塞,达到我们控制协程并发的效果 |
17 | go download(i, limiter, wg) |
18 | } |
19 | wg.Wait() |
20 | } |
21 | |
22 | func download(index int, limiter chan bool, wg *sync.WaitGroup) { |
23 | defer wg.Done() |
24 | fmt.Println("start to download :", index) |
25 | time.Sleep(1 * time.Second) |
26 | <-limiter |
27 | } |
总结
我总结的这些只是我目前为止所见所学,当然会有更加复杂的方式。希望能在后续工作中用到。