在Go里,每一个并发执行的活动称为goroutine。
当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine。
新的goroutine通过go语句进行创建。
语法上,一个go语句是在普通的函数或者方法调用前加上go关键字前缀。
go f()
示例:
package main
import (
"fmt"
"time"
)
func sayHello() {
for {
fmt.Println("Hello!")
time.Sleep(time.Millisecond * 500)
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x - 1) + fib(x - 2)
}
func main() {
go sayHello()
fmt.Println(fib(45))
}
示例中主goroutine顺序执行main函数,新建的goroutine顺序执行sayHello函数。
当main函数返回时,所有的gotoutine都暴力地直接终结,然后程序退出。
goroutine是Go程序并发的执行体,通道就是它们之间的连接。
通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
每一个通道是一个具体类型的通道。(如接收int类型消息的通道:chan int)
使用内置的make函数来创建一个通道:
ch := make(chan int)
像map一样,通道是一个使用make创建的数据结构的引用。
当复制或者作为参数传递到一个函数时,复制的是引用。
通道有主要两个操作:发送和接收。
发送就是一个goroutine传值到通道
接收就是一个goroutine接收通道里的值
ch := make(chan int)
ch <- x
a := <-ch //赋值语句中的接收表达式
<-ch //接收并丢弃结果
通道支持第三个操作:关闭。
它设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了。
一个通道关闭后的发送操作将导致宕机(panic)。
在一个已经关闭的通道上进行接收操作,将获取所有已经发送的值,直到通道为空。
这时任何接收操作会立即完成,同时获取到一个通道元素类型对应的空值。
内置的close函数关闭通道
close(ch)
无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上
执行接收操作,这时值传送完成,两个goroutine都可以继续执行。
所以如果接收操作先执行,接收方goroutine将会阻塞,直到发送方
goroutine在同一个通道上发送一个值。
使用无缓冲通道进行的通信导致发送和接收gotoutine同步化。
因此,无缓冲通道也称为同步通道。
示例:
package main
import (
"fmt"
)
func main() {
naturals := make(chan int)
squares := make(chan int)
go func() {
for x := 0; x < 100; x++ {
naturals <- x
}
close(naturals)
}()
go func() {
for {
x, ok := <-naturals
if !ok {
break
}
squares <- x * x
}
close(squares)
}()
for {
x, ok := <-squares
if !ok {
break
}
fmt.Println(x)
}
}
接收方会从通道收到两个信息:第一个是值,第二个是布尔值
true代表接收成功
false代表接收操作在一个已经关闭且读取完数据的通道上。
Go语言中提供range循环语法在通道上的迭代。
当通道关闭且数据读完时退出循环。
所以上面的例子可以改成
package main
import (
"fmt"
)
func main() {
naturals := make(chan int)
squares := make(chan int)
go func() {
for x := 0; x < 100; x++ {
naturals <- x
}
close(naturals)
}()
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
for x := range squares {
fmt.Println(x)
}
}
//单项通道
当通道作为函数参数列表时,有三种不的写法:
输出通道(只能输出),输入通道(只能输入),输入输出通道(都可)
func(c chan int)
//通道c可以输入输出(即发送方能给c发数据,接收方也能接收c中的数据)
func(in chan<- int)
//输入通道in(即仅支持发送方给in发数据)
func(out <-chan int)
//输出通道out(仅支持接收方接收out里的数据)
在任何赋值操作中双向通道可赋值给单项通道,反之不行。
//缓冲通道
缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make容量参数设置。
ch = make(chan string, 3)
缓冲通道的特点:
如果通道已满,发送方会阻塞
如果通道为空,接收方会阻塞
如果通道既不空也不慢,就都不会阻塞
内置函数cap可以获取通道的容量
//select
select的形式和switch很像
但select必须和通道一起使用
select {
case <-ch1:
//...
case x := <-ch2: //第二个参数可以不接收
//...
case ch3 <- y:
//...
default:
//...
}
select会一直等待,直到某一个case能够通信,并执行(会执行最快完成通信的那个)。
如果有default而其他case阻塞的话,就会直接执行default。