GoLang管道
# 🎈 缘起
最近准备用 golang
准备写一个客户端,然后在用界面传递命令给 golang
执行的时候发现还有个交互,七拐八拐的找到了 golang
中的管道似乎可以做到,但是不太熟悉,找了一个圈后发现了一篇博文写的很不错,我居然直接看懂了,为了防止对方网站关闭了,我就自行拷贝了一下。
# 定义
channel
(管道)是一个数据类型,主要用于 golang
协程同步以及数据共享的问题,golang
采用的是通信来进行内存共享。
# 定义 channel
变量
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity)
2
和 map
创建一样,channel
也是使用 make
创建的底层数据结构的引用。当复制一个 channel
变量时,两个变量都指向同一个底层数据结构。channel
的空值为 nil
当 capacity
为 0
时,channel
为 无缓冲;当 capacity
大于 0
时,channel
为 有缓冲,channel
有缓冲时是非阻塞的,所以必须要等待 channel
中的 capactiy
元素写满才结束阻塞。
# 使用 channel
channel <- value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同时检查通道是否已关闭或者是否为空
2
3
4
默认情况下,channel
接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得 goroutine
同步变的更加的简单,而不需要显式的 lock
。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
defer fmt.Println("子go程结束")
fmt.Println("子go程正在运行……")
for i := 0; i < 10; i++ {
fmt.Println("biu biu biu ", i)
c <- i
}
close(c)
}()
for {
num, ok := <-c //从c中接收数据,并赋值给num
if ok {
fmt.Println("num = ", num)
} else {
break
}
}
fmt.Println("main go程结束")
}
// 输出
// 子go程正在运行……
// biu biu biu 0
// biu biu biu 1
// num = 0
// num = 1
// biu biu biu 2
// biu biu biu 3
// num = 2
// num = 3
// biu biu biu 4
// biu biu biu 5
// num = 4
// num = 5
// biu biu biu 6
// biu biu biu 7
// num = 6
// num = 7
// biu biu biu 8
// biu biu biu 9
// num = 8
// num = 9
// 子go程结束
// main go程结束
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
48
49
50
51
52
53
54
# 无缓冲的 channel
无缓冲的通道(unbuffered channel
)是指在接收前没有能力保存任何数据值的通道。
这种类型的通道要求发送 goroutine
和接收 goroutine
同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine
阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
阻塞:由于某种原因数据没有到达,当前 go
程(线程)持续处于等待状态,直到条件满足,才解除阻塞。
同步:在两个或多个 go
程(线程)间,保持数据内容一致性的机制。
下图展示两个 goroutine
如何利用无缓冲的通道来共享一个值:
- 在第
1
步,两个goroutine
都到达通道,但哪个都没有开始执行发送或者接收。 - 在第
2
步,左侧的goroutine
将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine
会在通道中被锁住,直到交换完成。 - 在第
3
步,右侧的goroutine
将它的手放入通道,这模拟了从通道里接收数据。这个goroutine
一样也会在通道中被锁住,直到交换完成。 - 在第
4
步和第5
步,进行交换,并最终,在第6
步,两个goroutine
都将它们的手从通道里拿出来,这模拟了被锁住的goroutine
得到释放。两个goroutine
现在都可以去做其他事情了。
个人理解,无缓冲的通道就是一个异步操作,如果只有一个发送的而没有接收的,那么就会一直等待,直到有接收方。无缓冲的数据实际上是有一条数据的缓冲,打印的时候也是显示的结果也是一直现在在第一条数据等待,直到程序结束也没有第二条数据有进行循环打印
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
go func() {
defer fmt.Println("子go程结束")
fmt.Println("子go程正在运行……")
for i := 0; i < 10; i++ {
fmt.Println("biu biu biu ", i)
c <- i
}
close(c)
}()
for i := 0; i < 10; i++ {
fmt.Println("正在循环")
time.Sleep(2 * time.Second)
}
fmt.Println("main go程结束")
}
// 输出,注意下面的 biu biu biu 0 只有一条
// 正在循环
// 子go程正在运行……
// biu biu biu 0
// 正在循环
// 正在循环
// ...
// main go程结束
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
# 有缓冲的 channel
有缓冲的通道(buffered channel
)是一种在被接收前能存储一个或者多个数据值的通道。
这种类型的通道并不强制要求 goroutine
之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。
只有通道中没有要接收的值时,接收动作才会阻塞。
只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine
会在同一时间进行数据交换;有缓冲的通道没有这种保证。
- 在第
1
步,右侧的goroutine
正在从通道接收一个值。 - 在第
2
步,右侧的这个goroutine
独立完成了接收值的动作,而左侧的goroutine
正在发送一个新值到通道里。 - 在第
3
步,左侧的goroutine
还在向通道发送新值,而右侧的goroutine
正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。 - 最后,在第
4
步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。
个人理解,有缓冲也是一个异步操作,如果只有发送没有接收,那么就会一直等待,直到有接收方。有缓冲的数据是基础一条数据加上指定的数据量进行的缓冲,打印结果是如果我设置 make(chan int, 10)
时,那么会打印 11
条数据
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 10)
go func() {
defer fmt.Println("子go程结束")
fmt.Println("子go程正在运行……")
for i := 0; i < 10; i++ {
fmt.Println("biu biu biu ", i)
c <- i
}
close(c)
}()
for i := 0; i < 10; i++ {
fmt.Println("正在循环")
time.Sleep(2 * time.Second)
}
fmt.Println("main go程结束")
}
// 输出,注意下面的 biu biu biu 0 只有一条
// 正在循环
// 子go程正在运行……
// biu biu biu 0
// biu biu biu 1
// biu biu biu 2
// biu biu biu 3
// biu biu biu 4
// biu biu biu 5
// biu biu biu 6
// biu biu biu 7
// biu biu biu 8
// biu biu biu 9
// 子go程结束
// 正在循环
// 正在循环
// ...
// main go程结束
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
# 关闭 channel
如果发送者知道,没有更多的值需要发送到 channel
的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的 close
函数来关闭 channel
实现。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
for {
//ok为true说明channel没有关闭,为false说明管道已经关闭
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Finished")
}
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