What you are wasting today is tomorrow for those who died yesterday; what you hate now is the future you can not go back.
你所浪费的今天是昨天死去的人奢望的明天; 你所厌恶的现在是未来的你回不去的曾经。
Channel通道介绍
什么是channel, channel可以理解为goroutine之间通信的通道。一端发送数据方到达接收数据方的桥梁。
声明通道
每个channel都有一个与之相关联的类型, 这种类型是信道允许传输的数据类型。
channel的零值是nil, 一个nil channle没有任何用途。因此必须使用类似map和slice的方式定义。
package main
import "fmt"
func main() {
var A chan int
if A == nil {
fmt.Println("nil channle ,定义channle")
A = make(chan int)
fmt.Printf("Type of A is %T", A)
}
}
channel的发送和接收
chs := make(chan int)
chs <- 0 // 写入
i := <- chs // 接收
发送、接收阻塞
channel的发送和接收默认是阻塞的。当从channel读取数据时, main在读取时阻塞,知道有数据写入channel, 通道的发送也是如此。
通道的这个属性可以使goroutine有效地进行通信,而无需使用像其他编程语言中,很常见的显式锁或条件变量。
channel的使用
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
在这里我们使用sleep main进程的方式,阻塞main的继续执行,好让goroutine有时间执行 hello()内的内容。
但是这样做并不能有效的解决多个协程(main 和 goroutine)并行时出现的协程未执行完,main程退出问题。 如果hello()中的执行时间超过1S, 那么sleep就无实际意义了。
下面使用channle来改善以上程序:
package main
import (
"fmt"
)
var done chan bool
func hello() {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done = make(chan bool)
go hello()
<-done
fmt.Println("main function")
}
首先main程会初始化一个channle, 然后go hello() 创建一个新的协程,并立即执行<-done (前面我们说过,只是简单的调用,并不关心协程的输出,而立刻返回继续往下进行),并没有发送数据到channel done,所以main程阻塞。直到goroutine执行完成并发送数据到channle,main才会继续执行输出。
还有一点要注意的是:channel的接受可以不用赋值到任何变量,即有效的阻塞。
下面的程序会更好的理解channle:
package main
import (
"fmt"
"time"
)
func hello(done chan bool) {
fmt.Println("go hello() sleep ...")
time.Sleep(4 * time.Second)
fmt.Println("go hello() awake")
done <- true
}
func main() {
done := make(chan bool)
fmt.Println("Main going to call hello go goroutine")
go hello(done)
<-done
fmt.Println("Main received data")
}
程序会在goroutine中sleep后继续写入channle。
package main
import (
"fmt"
)
func add(number int, addChs chan int) {
number = number + 10
addChs <- number
}
func reduce(number int, reduceChs chan int) {
number = number - 10
reduceChs <- number
}
func main() {
number := 589
addChs := make(chan int)
reduceChs := make(chan int)
go add(number, addChs)
go reduce(number, reduceChs)
addVar, reduceVar := <-addChs, <-reduceChs
fmt.Println(addVar, reduceVar)
}
死锁
当程序中只有channle的读取或者发送的其中一个操作, 那么程序就会发生Deadlock。
package main
func main() {
ch := make(chan int)
<-ch
}
fatal error: all goroutines are asleep - deadlock!
单向通道
channel既可以发送也可以接收, 我们成为双向channle。 如果channle只能接收或者发送,就称为单向通道。
实例code说明一切:
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan<- int)
go sendData(sendch)
fmt.Println(<-sendch)
}
在mian程开始时,我们定义了一个只写(只发送)的channel, 但是后面main程又从中读取。所以导致go程序编译失败。
在上面我们看到sendData()接收到一个只写的channel ,那么如果我们传入的是双向的channel会发生什么?
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
chnl := make(chan int)
go sendData(chnl)
fmt.Println(<-chnl)
}
程序照常执行输出。为什么?
可以这么简单的理解一下: 因为在方法内(像java 作用域)sendData限制了sendch通道的读取,只能对channle进行写入操作,但是main程不属于同一作用域,sendData限制无效,main继续读取操作channle。
通道的关闭
发送端可以关闭通道,以便告诉接收方,不再发送数据。
接收方也可以在接收的同时,通过附加变量的方式检测channle的关闭状态。
v, ok := <- ch
如果开启状态下,ok为true, 否则为false。
在channel为关闭状态时, v(接收到的值)也是有值的,即channle type( 通道类型 )的零值。 比如 make(chan int) 零值就是0, make(chan string)零值就是"", make(chan map[string]int)呢?大家可以联想一下。
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)
// 试试将此处改用range代替
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}
前面我们说过,<- channel 通道的读取也会使程序阻塞,那么range操作呢?
package main
import (
"fmt"
)
func options(number int, dchnl chan int) {
for i:= 0 ;i< 10 ;i++ {
digit := number + i
dchnl <- digit
}
close(dchnl)
}
func box(number int, s chan int) {
sum := 0
dch := make(chan int)
go options(number, dch)
for digit := range dch {
sum += digit + digit
}
s <- sum
}
func main(){
s := make(chan int)
go box(1, s)
sVal := <-s
fmt.Println("Final output: ",sVal)
}
range读取channle同样可以阻塞程序,并不是单单<-channle。
The End.
有疑问加站长微信联系(非本文作者)