Golang入门学习(13)

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 了不会输出任何内容。

文章目录
  1. 1. go并发编程
    1. 1.1. 协程
    2. 1.2. 实例
    3. 1.3. 通道(channel)
      1. 1.3.1. 介绍
      2. 1.3.2. 发送接收特性
      3. 1.3.3. 语法
      4. 1.3.4. 示例
    4. 1.4. 协程同步
|