Chapter
Goroutines and Channels
Goroutines
第一个
gorountine
为main
使用
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
28package main
import (
"fmt"
"time"
)
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n)
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}除了退出之外没有其他办法使得一个
gorountine
停止另外一个,当然,依然可以通过gorountine
之间的交流来请求对方停止
- 我们常常使用函数字面量的方式,来启动一个
gorountine
,即go 函数定义(传入参数)
,而这个过程又常常涉及到闭包,由于函数字面量可能在函数体中引用外部的变量,如channel
Example: Concurrent Clock Server
Example: Concurrent Echo Server
Channels
创建一个
channel
1
ch := make(chan int) // 创建了一个名为ch的channel,其中传递的数据类型为int
channel
的基本操作- Send:
ch <- x
,将x的值传入ch
- Receive
x = <-ch
, 从ch
接收值并赋给变量x
,也可以不设置接收对象,应该等同于_ = <-ch
,即抛弃接收的值作为函数返回值的时候不需要声明临时变量,
return <- ch
即可 - Close
close(ch)
将会关闭一个channel,对一个已经关闭的channel进行Send
操作将会产生panic
Receive
操作将会获取可能已经传入的值,如果没有则获得的为对应类型的零值,(比如发送方在send之后立即关闭,此时可能接收方还没有收到,但channel已经被关闭,那么接收方依然可以对这个channel执行receive操作,直到没有值可以取出,可以理解为邮箱?)。在Receive
操作中可以选择接收两个值,x, ok = <-ch
其中第二个值ok
就可以用来表征是否已经取出了所有的值。当然由于这种需求非常普遍,所以go提供了range
操作符,可以直接用来遍历通过channel传递的数据,在结束后自动跳出
- Send:
channel又分为buffered
/unbuffered
两种,以下分别介绍这两种channel
Unbuffered Channels
简单来说就是同步的,针对某一次发送,接收方没有确认接收的话,发送方将会被阻塞,对接收方也是一样,当尝试接收的时候,接收反进程将会一直等待到收到数据为止。
其中go确保接收方的接收操作早于发送方被唤醒
一般来说我们使用channel是为了传递信息,但
unbuffered channel
自身的同步特性使得我们可以以一种类似于同步锁的方式来使用channel,此时传递的数据其实不包含任何信息,数据类型常常选择一些简单的类型,如int
Buffered Channels
基本形式
1
2
3ch = make(chan int) // unbuffered channel
ch = make(chan int, 0) // unbufferd channel
ch = make(chan int, 3) // bufferd channel with capacity 3简单来说就是当缓冲区为空的时候,再次尝试接收,接收者会被阻塞,当缓冲区满了,再次尝试发送,发送者会被阻塞,其实很像操作系统中学习的信号灯。
对buffered channel 可以使用
len
函数,返回缓冲区中元素的个数,同理可以使用cap
获得缓冲区的大小
Unidirectional Channel Types
单项的channel。当函数接收channel作为参数的时候,往往同一个channel参数只会使用接收/发送操作,也即在该函数中,channel是单向的。
用法如下:
1 | func squarer(out chan<- int, in <-chan int) |
其中out只能用于输出(向channel输入),in只能作为输入(从channel中获得),参数的形式其实已经揭示了其用法,out chan <- int
就意味着对于out
只能使用形如out <- someValue
的操作
Looping in Parallel
Example: Concurrent Web Crawler
Multiplexing with select
Example: Concurrent Directory Traversal
Cancellation
Example: Chat Server
以上几章的内容偏向于实践,包含大量代码,建议直接查看原文。
其中Cancellation一节主要讲的是可以利用channel的close
操作所带来的间接的广播效果(所有使用该channel的函数可以通过receive操作来获取知晓该channel被关闭,进而实现某种意义上的通知),来实现对所有的gorountine的取消(对应的gorountine内部需要编写对应的相应代码)