信道定义
信道是Go协程之间通信的管道。通过使用信道,数据可以从一端发送,在另一端接收。
var name chan T
通过name 关键字声明一个新的channel,并且声明时指定channel传输的数据类型T。
创建channel时我们需要借助make函数对channel进行初始化。
ch := make(chan T,size)
创建channel时需指定channel的传输的数据类型T,如果指定size,即指定channel的长度,即信道指定了缓冲区。
channel发送和接收数据
channel作为一个队列,它会保证数据遵循先入先出的原则进行,且同一时刻有且仅有一个goroutine访问channel发送和获取数据。
发送数据格式:
ch <- val // 将val发送到信道ch
在信道被填满之后再向信道中发送数据会阻塞当前goroutine.
接收数据格式:
val := <- ch //表示从信道ch读取一个值并赋值给val
信道用于协程的一个小例子:
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("goroutine 1")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")
}
创建了一个 bool 类型的信道 done
,并把 done
作为参数传递给了 hello
协程。我们通过信道 done
接收数据。此时代码发生了阻塞,除非有协程向 done
写入数据,否则程序不会跳到下一行代码。输出如下:
goroutine 1
main function
如果channel ch中没有数据,将会阻塞读取到goroutine,直到有数据放入channel,也可以在读取channel时判断是否获取到数据。
val,ok := <- ch
检查ok是否为true,用于判断是否读取到了有效数据。从关闭的信道读取到的值会是该信道类型的零值。例如,当信道是一个 int
类型的信道时,那么从关闭的信道读取的值将会是 0
。
package main
import (
"fmt"
)
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
输出如下:
Received 0 true
Received 1 true
Received 2 true
Received 3 true
Received 4 true
Received 5 true
Received 6 true
Received 7 true
Received 8 true
Received 9 true
死锁
使用信道需要考虑的一个重点是死锁。当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。
同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。
package main
func main() {
ch := make(chan int)
ch <- 2
}
输出如下:
fatal error: all goroutines are asleep - deadlock!
程序中创建的信道ch
,通过ch <-2
把2发送到信道,但没有其他协程从ch接收数据,于是程序触发了panic
.
单向信道
只能发送或接收数据的信道称为单向信道。
只写信道定义:
type Sender chan<- T
只读信道定义:
type Reciver <-chan T
T为信道的数据类型。如果向只读信道中接收数据,编译器会报错。同理,向只写信道中读取数据也会报错。
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan<- int)
go sendData(sendch)
fmt.Println(<-sendch)
}
输出如下:
invalid operation: <-sendch (receive from send-only type chan<- int)
创建的只写(send-only)信道sendch,<-sendch
试图通过send-only信道接收数据,于是编译器报错。下面是正确使用的一个例子:
package main
import "fmt"
func main() {
c := make(chan int)
var readc <- chan int = c
var writec chan <- int = c
go SetChan(writec)
GetChan(readc)
}
func SetChan(writec chan <- int) {
for i:=0;i<5;i++ {
writec <- i
}
}
func GetChan(readc <- chan int) {
for i:=0;i<5;i++ {
fmt.Printf("我是GetChan函数,从SetChan返回的信息是%d\n",<- readc)
}
}
输出如下:
我是GetChan函数,从SetChan返回的信息是0
我是GetChan函数,从SetChan返回的信息是1
我是GetChan函数,从SetChan返回的信息是2
我是GetChan函数,从SetChan返回的信息是3
我是GetChan函数,从SetChan返回的信息是4
带缓冲区的channel
无缓冲信道的发送和接收过程是阻塞的。创建数据时若指定了channel的长度那么channel将会拥有缓冲区。只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。例如:
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 2)
ch <- 0
ch <- 2
fmt.Println(<- ch)
fmt.Println(<- ch)
}
输出如下:
0
2
可以看到上面的程序并未报错,程序未发生阻塞。
下面一个示例来理解当向缓冲信道中写入数据时,什么时候会发生阻塞。
package main
import (
"fmt"
"time"
)
func write(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("successfully wrote", i, "to ch")
}
close(ch)
}
func main() {
ch := make(chan int, 2)
go write(ch)
time.Sleep(2 * time.Second)
for v := range ch {
fmt.Println("read value", v,"from ch")
time.Sleep(1 * time.Second)
}
}
write
协程里立即会向 ch
写入 0 和 1,接下来发生阻塞,直到 ch
内的值被读取。因此,该程序立即打印出下面两行:
successfully wrote 0 to ch
successfully wrote 1 to ch
打印上面两行之后,write
协程中向 ch
的写入发生了阻塞,直到 ch
有值被读取到。而 Go 主协程休眠了两秒后,才开始读取该信道,因此在休眠期间程序不会打印任何结果。主协程结束休眠后,在使用 for range 循环,开始读取信道 ch
,打印出了读取到的值后又休眠两秒,这个循环一直到 ch
关闭才结束。所以该程序在两秒后会打印下面两行:
read value 0 from ch
successfully wrote 2 to ch
该过程会一直进行,直到信道读取完所有的值,并在 write
协程中关闭信道。最终输出如下:
successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch
什么是 select?
select
语句用于在多个发送/接收信道操作中进行选择。select
语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select
会随机地选取其中之一执行。该语法与 switch
类似,所不同的是,这里的每个 case
语句都是信道操作。
package main
import "fmt"
func main() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
ch3 := make(chan int, 1)
ch1 <- 1
ch2 <- 1
ch3 <- 1
select {
case <- ch1:
fmt.Println("ch1")
case <- ch2:
fmt.Println("ch2")
case <- ch3:
fmt.Println("ch3")
default:
fmt.Println("没有满足的ch")
}
}
输出如下:
ch1