0%

Golang并发控制

前言

最近决定研究下 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
}

总结

我总结的这些只是我目前为止所见所学,当然会有更加复杂的方式。希望能在后续工作中用到。