19、goroutine和并发-GOLANG

启动goroutine

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

import (
"fmt"
"time"
)

func sleepyGopher() {
time.Sleep(3 * time.Second)
fmt.Println("...zzZZ...")
}
func main() {
go sleepyGopher() //启动goroutine
time.Sleep(4 * time.Second)
} //main函数结束所有的goroutine都会结束,所以我们需要在main函数最后加一个sleep等待goroutine结束

多个goroutine

每次使用go关键字都会产生一个新的goroutine,从表面看好像是所有的goroutine同时运行,实际上每次运行只有操作系统和处理器会知道,所以我们使用goroutine时只能假设不同的goroutine以任意顺序执行

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

import (
"fmt"
"time"
)

func sleepyGopher(i int) {
time.Sleep(3 * time.Second)
fmt.Println(i, "...zzZZ...")
}
func main() {
for i := 0; i < 5; i++ {
go sleepyGopher(i)
}
time.Sleep(4 * time.Second)
}

image-20230312161445567

通道

上面的例子不难看出goroutine执行的时候我们用延迟等的方法很不合理,一旦程序复杂人力根本无法判断该延迟多久等goroutine执行完

通道(channel),可以在多个goroutine之间安全的传递值,和go的其他类型一样,你可以把channel用作变量,传递给其他函数等等

与创建切片与映射一样,创建通道需要用到内置的make函数,并且需要指定类型

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

import (
"fmt"
"time"
)

func sleepyGopher(i int, c chan int) {
time.Sleep(3 * time.Second)
fmt.Println(i, "...zzZZ...")
c <- i
}
func main() {
c := make(chan int)
for i := 0; i < 5; i++ {
go sleepyGopher(i, c)
}
for i := 0; i < 5; i++ {
gopherID := <-c
fmt.Println("gopher", gopherID, "has finished sleeping")
}

}

image-20230312163826847

使用select处理多个通道

上述例子适合我们使用了单个通道等待多个goroutine,这种做法前提是这些goroutine都产生相同类型值,但是在实际中,程序通常需要等待多种类型的值

还有时我们不愿意等待太久,等候固定的一段时间时选择放弃,而不是一直耗着,go语言标准库提供了 time.After来帮助我们

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
package main

import (
"fmt"
"time"
)

func sleepyGopher(i int, c chan int) {
//time.Sleep(time.Duration(rand.Intn(4000)) * time.Millisecond)
c <- i
}
func main() {
c := make(chan int)
timeout := time.After(5 * time.Second)
for i := 0; i < 5; i++ {
//go sleepyGopher(i, c)//未调用通道没有数值,c接收不到值就会一直等
select {
case gopherID := <-c:
fmt.Println("gopher", gopherID, "has finished sleeping")
case <-timeout://超过5秒直接跳出循环,结束程序
fmt.Println("my patience ran out")
return
}
}

}

阻塞和死锁

当goroutine在等待通道的发送或者接受操作的时候,我们就说他被阻塞了,

当一个或者多个goroutine因为某些永远无法发生的事情而被阻塞时,我们称这种情况为死锁,出现死锁的程序通常回崩溃或者被挂起

1
2
3
4
func main(){
c := make(chan int)
<-c
}//死锁

装配线

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
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"strings"
)

func sourceGopher(downstream chan string) {
for _, v := range []string{"hello world", "a bad apple", "goodbye all"} {
downstream <- v
}
downstream <- ""
}

func filterGopher(upstream, downstream chan string) {
for {
item := <-upstream
if item == "" {
downstream <- ""
return
}
if !strings.Contains(item, "bad") {
downstream <- item
}
}
}
func printGopher(upstream chan string) {
for {
v := <-upstream
if v == "" {
return
}
fmt.Println(v)
}
}

func main() {
c0 := make(chan string)
c1 := make(chan string)
go sourceGopher(c0)
go filterGopher(c0, c1)
printGopher(c1)
}

程序使用了空字符串表示所有数值均已发送完毕,但是当他需要处理包含空字符串的字符串时就裂开,所以我们需要想一个新的办法来判断

go允许我们关闭没有值可供发送的通道,通道被关闭后如果尝试写入值会引发惊恐,尝试读取值则会返回一个通道类型的零值,这个零值就可以代替上述代码中的空字符串

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
31
32
33
34
35
36
package main

import (
"fmt"
"strings"
)

func sourceGopher(downstream chan string) {
for _, v := range []string{"hello world", "a bad apple", "goodbye all"} {
downstream <- v
}
close(downstream)
}

func filterGopher(upstream, downstream chan string) {
for item := range upstream {//for range 可以遍历通道(channel),但是通道在遍历时,只输出一个值,即管道内的类型对应的数据
if !strings.Contains(item, "bad") {
downstream <- item
}
}
close(downstream)
}
func printGopher(upstream chan string) {
for k := range upstream {
fmt.Println(k)
}
}

func main() {
c0 := make(chan string)
c1 := make(chan string)
go sourceGopher(c0)
go filterGopher(c0, c1)
printGopher(c1)
}