Go的第13课——Go 并发

这几天元旦在外面浪呢,也是很长一段时间没有学习,今天来补补。

go并发编程

协程

Golang中的并发是 函数 相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了Goroutines作为并发处理操作的一种方式。 创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字:

1
go function(1,2,3)

相当于是让 function 异步执行,后续语句不等待 function(1,2,3) 执行完毕。

就相当于是开一个线程去执行函数内容,等同于 python 中的 threading.Thread(target=funciton,args=[1,2,3]).start()

实例

为了起到效果,是用了 time 包里的 sleep 函数。

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
44
45
46
47
package main

import (
"fmt"
"time"
)

func test1() {
for i := 0; i < 5; i++ {
fmt.Println("test1...")
time.Sleep(1)
}
}
func test2() {
for i := 0; i < 5; i++ {
fmt.Println("test2...")
time.Sleep(1)
}
}
func test3() {
for i := 0; i < 5; i++ {
fmt.Println("test3...")
time.Sleep(1)
}
}
func main() {
go test1()
go test2()
test3()
}
/*
test3...
test2...
test1...
test1...
test2...
test3...
test2...
test1...
test3...
test2...
test3...
test1...
test1...
test3...
test2...
*/

运行结果为交替输出,这就是并发编程。

通道(channel)

介绍

Go 提供了一种称为通道的机制,用于在 goroutine 之间共享数据。当您作为goroutine 执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。

需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个 goroutine 可以访问数据项:因此按照设计不会发生数据竞争。

根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine 之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换。缓冲通道没有这样的保证。

可以理解为 socket 或者是多进程通信的 pipe

通道由 make函数创建,该函数指定 chan 关键字和通道的元素类型。

发送接收特性

所谓有缓冲区就是指可以建立一个临时的缓冲区,收和发可以不同步,我发了一个消息可以等你上线查看,无缓冲区就是指你必须立刻接收这些消息,没有缓冲区留存数据,发的时候你要是没有在接收那就没有了,当然在这里他会强制发的人等待接收的人做接收动作,或者是等待发的人发出消息。

  • 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。
  • 发送操作和接收操作中对元素值的处理都是不可分割的。
  • 发送操作在完全完成之前会被阻塞。接收操作也是如此。

语法

1
2
Unbuffered:= makechan int//整型无缓冲通道
buffered:=makechan int,10//整型有缓冲通道

使用内置函数 make 创建无缓冲和缓冲通道。make 的第一个参数需要关键字 chan,然后是通道允许交换的数据类型。

发送数据:

1
channel<-data

接收数据:

1
data:=<-channel

示例

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

import (
"fmt"
"math/rand"
)

var values = make(chan int)

func send() {
v := rand.Int()
values <- v
fmt.Println("send value:", v)

}
func main() {
defer close(values)
go send()
v := <-values
fmt.Println("recv value:", v)
}
/*
send value: 5577006791947779410
recv value: 5577006791947779410
*/

协程同步

使用 WaitGroup 来进行协程同步。

sync.WaitGroup类

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

import (
"fmt"
"math/rand"
"sync"
"time"
)

var wg sync.WaitGroup

func test(i int) {
defer wg.Done() //每次结束让 wg -= 1
fmt.Printf("routing %v\n", i)
rand.Seed(time.Now().UnixNano())
times := rand.Intn(3)
//fmt.Printf("sleep:%v\n", times)
time.Sleep(time.Second * time.Duration(times))

}

func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go test(i)
}
wg.Wait() //等待所有协程完毕
fmt.Println("Done...")
}
/*
routing 8
routing 1
routing 3
routing 4
routing 5
routing 6
routing 0
routing 7
routing 9
routing 2
Done...
*/

同时我们可以观察观察 wg.Wait() 这句删除之后会发生什么,极大概率是只会输出一个 Done... 的,因为主线程结束之后所有协程不管在哪都默认 kill 了不会输出任何内容。