Go有两种并发编程的风格。这一章展示goroutine和通道(channel),它们支持通信顺序进程,CSP是一个并发的模式,在不同的执行体直接传递值,但是变量本身局限于单一的执行体。Go的长处是处理并发请求,并发编程在本质上比顺序编程要困难一些,从顺序编程的直觉让我们觉得贼迷茫。
在Go里面,每一个并发执行的活动成为goroutine。假设有一个程序,它有两个函数,一个是用来做计算,一个是用来做打印,他们互不调用,于是他们可以同时执行,可以在两个goroutine的并发程序中。当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine。新的goroutine通过go语句进行创建。语法上,一个go语句是在普通的函数或者方法调用前加上go关键字前缀。go语句使函数在一个新创建的goroutine中调用。go语句本身的执行立即完成:
f(); // 调用f(),等待它返回
go f();// 新建一个调用f()的goroutine,不用等待
我们拿计算45个斐波那契数为例子,因为它使用非常低效的递归算法,所以需要大量的时间来计算。
func main(){
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d", 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)
}
如果说goroutine是Go程序并发的执行体,通道就是它们之间的连接。通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有int类型元素的通道写作chan int,使用内置的make函数来创建一个通道的方法:
ch := make(chan int)// ch的类型是chan int
像map一样,通道是一个使用make创建的数据结构的引用,当复制或者作为参数传递到一个函数的时候,复制的是引用,这样调用者和被调用者都引用同一个数据的数据结构。和其他引用类型一样,通道的零值是nil。同种类型的通道可以使用==符号进行比较。当二者都是同一通道数据的引用时,比较值为true。通道也可以和nil进行比较。通道的两个主要操作是发送和接收,两者统称为通信。send语句从一个goroutine传输一个值到另外一个在执行接收表达式的goroutine。两个操作都使用< - 操作符书写。发送语句中,通道和值分别在<- 的左右两边。在接收表达式中, <- 放在通道操作数前面。在接收表达式中,其结果未被使用也是合法的。
ch <- x // 发送语句
x = <- ch // 赋值语句中的接收表达式
<- ch // 接收语句,丢弃结果
通道还支持第三个操作:关闭,它设置一个标志位来指示当前已经发送完毕,这个通道后面就没有值了,关闭后的发送操作会导致宕机,调用的方法为:
close(ch)
使用简单的make调用创建的通道叫做无缓冲通道,但make还可以接受第二个可选参数,一个表示通道容量的整数,如果容量是0,make创建一个无缓冲通道:
ch = make (chan int) //无缓冲通道
ch = make (chan int, 0) // 无缓冲通道
ch = make (chan int, 3) // 容量为3的缓冲通道
有疑问加站长微信联系(非本文作者)