chan用于goroutine间的通信
1、基础
有缓冲通道和无缓冲通道
无缓冲通道:make(chan int)
有缓冲通道:make(chan int, 5)
无缓冲通道会导致发送和接收的goroutine同步化
主要体现在如下两点:
- 无缓冲通道会使发送goroutine阻塞,直到对应的chan上有接收者
package main
import (
"fmt"
"time"
)
var ch chan int
func main(){
var i int = 9
ch = make(chan int)
go waiting()
fmt.Println("向chan发送,时间:",time.Now())
ch <- i
fmt.Println("发送完毕,时间:", time.Now())
//ch <- i
time.Sleep(10000000000)
}
func waiting(){
for {
time.Sleep(10000000000)//接收协程等待10秒
i, ok := <-ch
if !ok {
fmt.Println("chan is close!")
break
}
fmt.Println("recv:", i)
}
fmt.Println("waiting end!")
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
向chan发送,时间: 2017-12-07 16:12:04.856740287 +0800 CST
recv: 9
发送完毕,时间: 2017-12-07 16:12:14.857350085 +0800 CST
winterdeMacBook-Pro:chantest winter$
- 同样,无缓冲通道会使接收goroutine阻塞,直到对应的chan上有发送
package main
import (
"fmt"
"time"
)
var ch chan int
func main(){
var i int = 9
ch = make(chan int)
go waiting()
fmt.Println("向chan发送,时间:",time.Now())
time.Sleep(10000000000)//等待10秒后向chan发送数据
ch <- i
fmt.Println("发送完毕,时间:", time.Now())
//ch <- i
time.Sleep(10000000000)
}
func waiting(){
for {
//time.Sleep(10000000000)//接收协程等待10秒
fmt.Println("开始接收chan中的数据,时间:", time.Now())
i, ok := <-ch
fmt.Println("chan中有数据,时间:",time.Now())
if !ok {
fmt.Println("chan is close!")
break
}
fmt.Println("recv:", i)
}
fmt.Println("waiting end!")
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
向chan发送,时间: 2017-12-07 16:17:35.598050582 +0800 CST
开始接收chan中的数据,时间: 2017-12-07 16:17:35.598060998 +0800 CST
发送完毕,时间: 2017-12-07 16:17:45.602548482 +0800 CST
chan中有数据,时间: 2017-12-07 16:17:45.602599793 +0800 CST
recv: 9
开始接收chan中的数据,时间: 2017-12-07 16:17:45.602665344 +0800 CST
缓冲通道,相当与一个队列,队列的最大长度在创建时指定
- 一个空的chan,会导致接收goroutine阻塞
- 一个满的chan,会导致发送goroutine阻塞
2、会导致程序挂掉的异常
nil通道的发送和接收
chan的零值是nil,没有初始化的等于nil
package main
import (
"fmt"
)
func main(){
var ch chan int
if ch == nil {
fmt.Println("ch is nil")
}
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
ch is nil
nil值的chan上的发送和接收会导致程序挂掉并提示“nil chan”
package main
import (
"fmt"
)
var ch chan int
func main(){
var i int = 9
go waiting()
ch <- i
}
func waiting(){
i:= <-ch
fmt.Println("recv:", i)
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send (nil chan)]:
main.main()
/Users/winter/code/go_project/src/test/chantest/chantest1.go:10 +0x64
goroutine 5 [chan receive (nil chan)]:
main.waiting()
/Users/winter/code/go_project/src/test/chantest/chantest1.go:15 +0x50
created by main.main
/Users/winter/code/go_project/src/test/chantest/chantest1.go:9 +0x35
向没有接收者的通道发送
向没有接受者的通道发送数据,程序会挂掉,会提示“deadlock”
package main
import (
"fmt"
)
var ch chan int
func main(){
var i int = 9
ch = make(chan int)
//ch没有接收者
ch <- i
}
func waiting(){
i:= <-ch
fmt.Println("recv:", i)
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/Users/winter/code/go_project/src/test/chantest/chantest1.go:11 +0x7f
chan后的关闭(close)操作
- 在一个关闭后的chan上进行接收操作,将获取所有已经发送的值,直到chan为空,然后阻塞解除,会一直获取到chan元素类型对应的零值。即从一个关闭的chan上读取数据,永远不会阻塞。
- 在接收端可以判断chan是否关闭,但是没有办法在发送端判断chan是否关闭,所以只能在发送端关闭chan。
- 关闭chan是非必需的,因为GC回收chan,不是根据chan是否关闭,而是根据是否可以访问来决定的
- 一般情况下,不要关闭chan,因为关闭一个chan存在以下风险:
关闭一个已经关闭的chan会导致程序崩溃
关闭一个零值chan会导致程序崩溃
chan关闭后,发送操作会导致程序崩溃
package main
import (
"fmt"
"time"
)
var ch chan int
func main(){
var i int = 9
ch = make(chan int)
go waiting()
ch <- i
close(ch)
ch <- i
time.Sleep(10000000000)
}
func waiting(){
for {
i:= <-ch
fmt.Println("recv:", i)
}
}
输出:
winterdeMacBook-Pro:chantest winter$ ./chantest1
recv: 9
recv: 0
recv: 0
recv: 0
recv: 0
recv: 0
recv: 0
panic: send on closed channel
goroutine 1 [running]:
main.main()
/Users/winter/code/go_project/src/test/chantest/chantest1.go:14 +0xda
可见,接收的goroutine收到最后一个元素后,一直获取到int的零值。直到主goroutine往关闭的chan发送导致程序挂掉。
那么如何判断一个chan是否已经被close?从chan读取数据,一般都简写成 i := <- ch
,其实返回两个值,例如i, ok := <-ch
,第一个返回值表示获取的元素,第二个返回值是bool型,等于false表示读取异常。所以可以根据这个返回值判断chan是否被close。
package main
import (
"fmt"
"time"
)
var ch chan int
func main(){
var i int = 9
ch = make(chan int)
go waiting()
ch <- i
close(ch)
//ch <- i
time.Sleep(10000000000)
}
func waiting(){
for {
i, ok := <-ch
if !ok {
fmt.Println("chan is close!")
break
}
fmt.Println("recv:", i)
}
fmt.Println("waiting end!")
}