Goruntines
在 go 语言中,每一个并发的执行单元叫作一个 goroutine 。
- goroutin 生成:
一 个 go 生成一个协程
当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine 。新的 goroutine 会用 go 语句来创建。
- goroutin 结束:
每一个 goroutine 都是独立的。新的 gorountine 进行过程中, main goroutine 也会继续执行,当 main goroutine 执行结束后,所有的 goroutine 都结束 。
主函数返回时, 所有的 goroutine 都会被直接打断, 程序退出。除了从主函数退出或者直接终止程序之外, 没有其他的编程方法能够让一个 goroutine 来打断另一个执行, 但是之后可以看到一种方式来实现这个目的,通过 goroutine 之间的通信来让一个 goroutine 请求其他的 goroutine,并让被请求的 goroutine 自行结束执行。
//启了一个 goroutine 去执行 spinner(), 但 main goroutine 还在执行
//故 main goroutine 执行完最后一句 fmt.Printf() 后,子协程也需要结束,尽管还在 sleep()。
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
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)
}
Channels
- channel 介绍:
channels 是 goroutine 之间的通信机制。
一个 channel 是 一个通信机制,它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型, 也就是 channel 可发送的数据。 一个可以发送 int 类型数据的 channel 一般写为 chan int。
使用内置的 make 函数, 我们可以创建一个 channel:
ch := make(chan int) // ch has type 'chan int',不带缓存
ch := make(chan int, 3) // ch has type 'chan int',带缓存
- channel使用—本质上是使用引用
和map类似,channel也对应一个make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
- channel 运算符
-
==
两个相同类型的channel可以使用 == 运算符比较 -
<-
一个channel有发送和接受两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都使用<-运算符。
ch <- x // 发送语句
x = <-ch // 接受语句
<-ch // 接受语句,也合法
- close() 关闭channel
close(ch)
不带缓存的Channels
基于无缓存 Channels 的发送和接收操作将导致两个 goroutine 做一次同步操作。
一个基于无缓存 Channels 的发送操作将导致发送者 goroutine 阻塞,直到另一个 goroutine 在相同的 Channels 上执行接收操作,当发送的值通过 Channels 成功传输之后,两个 goroutine 可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者 goroutine 也将阻塞,直到有另一个 goroutine 在相同的 Channels 上执行发送操作。
串联的Channels ( Pipeline )
Channels 也可以用于将多个 goroutine 连接在一起,一个 Channel 的输出作为下一个 Channel 的输入。这种串联的 Channels 就是所谓的管道( pipeline )。下面的程序用两个 channels 将三个 goroutine 串联起来
第一个goroutine是一个计数器,用于生成0、1、2、……形式的整数序列,然后通过channel将该整数序列发送给第二个goroutine;
第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine;
第三个goroutine是一个打印程序,打印收到的每个整数。
为了保持例子清晰,我们有意选择了非常简单的函数,当然三个goroutine的计算很简单,在现实中确实没有必要为如此简单的运算构建三个goroutine。
func main() {
naturals := make(chan int)
squares := make(chan int)
// Counter
go func() {
for x := 0; x < 100; x++ {
naturals <- x
}
close(naturals)
}()
// Squarer
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
// Printer (in main goroutine)
for x := range squares {
fmt.Println(x)
}
}
单方向的Channel
随着程序的增长,人们习惯于将大的函数拆分为小的函数。
func counter(out chan int)
func squarer(out, in chan int)
func printer(in chan int)
为了表明这种意图并防止被滥用,Go语言的类型系统提供了单方向的 channel 类型,分别用于只发送或只接收的 channel 。类型 chan<- int 表示一个只发送 int 的 channel ,只能发送不能接收。相反,类型 <-chan int 表示一个只接收 int 的 channel ,只能接收不能发送。(箭头<-和关键字 chan 的相对位置表明了 channel 的方向。)这种限制将在编译期检测。
func counter(out chan<- int) {
for x := 0; x < 100; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
}
func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}
备注:调用 counter(naturals)时,naturals 的类型将隐式地从 chan int 转换成 chan<- int 。调用 printer(squares) 也会导致相似的隐式转换,这一次是转换为 <-chan int 类型只接收型的 channel 。任何双向 channel 向单向 channel 变量的赋值操作都将导致该隐式转换。这里并没有反向转换的语法:也就是不能将一个类似 chan<- int 类型的单向型的 channel 转换为 chan int 类型的双向型的 channel 。
带缓存的Channels
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。
ch = make(chan string, 3)
向缓存 Channel 的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个 goroutine 执行接收操作而释放了新的队列空间。相反,如果 channel 是空的,接收操作将阻塞直到有另一个 goroutine 执行发送操作而向队列插入元素。