Go concurrency(并发)

1. 简介

goroutineGo 中的并发执行单位,可以理解为“线程”,但它不是“线程”。生成一个 goroutine 非常简单,只需 go 一下就实现了。在同一个程序中所有的 goroutine 共享一个地址空间。goroutine 通过通信来共享内存,而不是共享内存来通信。

channel 是各个 goroutine 之间通信的管道,它是引用类型,可以使用 == 进行比较,如果引用了相同的数据结构,则结果为真。传数据用 channel <- data,取数据用 <- channel。多数情况下,它是阻塞同步的。channel 可以设置为单向或双向,也可以设置缓存大小,在未被填满前不会发生阻塞。

select 可处理一个或多个 channel 的发送与接收,同时有多个可用的 channel 时按随机顺序处理。

2. 基本使用

样例 1 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

func main() {
ch := make(chan bool)
go func() {
fmt.Println("Go go go!")
ch <- true
}()
<- ch
}

go func() 启动了一个 goroutinemain 函数执行到 <- ch 时会一直等待直到取得管道中的值,当打印出 “Go go go!“ 之后,才会传递值到管道中,这时候,程序才执行结束。这是一个简单的示例,我们不需要手动关闭管道,程序结束后占用的资源会自动释放。

样例 2 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

func main() {
ch := make(chan bool)
go func() {
fmt.Println("Go go go!")
ch <- true
close(ch)
}()

for v := range ch {
fmt.Println(v)
// print "true"
}
}

for range 会不断地迭代管道中的值,知道管道被关闭。如果我们删掉 close(ch) 这行代码,会导致死锁。

2. 设置缓存

无缓存是同步阻塞的,有缓存是异步的。

样例 3 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"runtime"
)

func main() {
// 多核运行
runtime.GOMAXPROCS(runtime.NumCPU())

ch := make(chan bool, 10)
for i := 0; i < 10; i++ {
go Go(ch, i)
}
for i := 0; i < 10; i++ {
<- ch
}
}

func Go(ch chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += 1
}
fmt.Println(a)

ch <- true
}

上例设置了一个缓存为 10 的管道,实现了多个 goroutine 的并发执行。除了 channel ,我们还可以用 WaitGroup 实现同样的效果,见样例 4 。

样例 4 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"runtime"
"sync"
)

func main() {
// 多核运行
runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)

for i := 0; i < 10; i++ {
go Go(&wg)
}

wg.Wait()
}

func Go(wg *sync.WaitGroup) {
a := 1
for i := 0; i < 100000000; i++ {
a += 1
}
fmt.Println(a)

wg.Done()
}

3. select 的用法

selectGo 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个只能用于 channel 的通信操作,要么是发送要么是接收。

select 如果满足多个分支条件,则会随机执行一个 case 。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

样例 5 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"time"
)

func main() {
c1 := make(chan string)
c2 := make(chan string)

go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()

go func() {
time.Sleep(2 * time.Second)
c1 <- "two"
}()

for i := 0; i < 2; i++ {
select {
case msg1 := <- c1:
fmt.Println("received", msg1)
case msg2 := <- c2:
fmt.Println("received", msg2)
}
}
}

样例 6 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}

func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
您的支持将鼓励我继续创作!
0%