chan 的类型
- 有缓冲chan
- 无缓冲chan
- nil chan
我从来没有用过这个功能,但是想想哪些令人恶心的面试官,是不是觉得没用过也得准备 @_~
可能前面两个你张口就来,但是对于第三个你可能需要稍加思索了,应该怎么定义一个nil chan,它可以被读吗?可以被写吗,它可以被close吗?
chan 的定义
// 有缓冲的chan
var ch1 =make(chan int,5)
// 无缓冲的chan 方式 1
var ch2 = make(chan int)
// 无缓冲的chan 方式 2
var ch3 make(chan int, 0)
// nil chan 的定义
var ch4 chan int
chan 的读写
有缓冲读
- 在同一个协程内(仔细想想,其实应该没有人会这么干,但是一旦这么干会有什么结果呢):
- 如果刚创建了就直接读,其实会导致程序崩溃
package main
import (
"fmt"
)
func main() {
var ch3 = make(chan int,5)
fmt.Println(<-ch3)
}
//-------------------------
/*
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/home/xd/code/demo/chan.go:7 +0x3c
exit status 2
*/
- 不在同一个协程内
如果chan里没有内容,读会阻塞。
package main
import (
"fmt"
"time"
)
func main() {
var ch3 = make(chan int,1)
ch3<-2
go func() {
for {
fmt.Println("start reading")
fmt.Println(<-ch3)
fmt.Println("read done")
}
}()
time.Sleep(time.Second*4)
}
/*
start reading
2
read done
start reading
*/
有缓冲写
对于有缓冲的chan,即便是没有协程进行读取也可以一直写,直到写满为止,写满以后阻塞
package main
import (
"fmt"
"time"
)
func main() {
var ch3 = make(chan int,13)
ch3<-2
ch3<-2
ch3<-2
ch3<-2
fmt.Println("write done: len= ", len(ch3))
time.Sleep(time.Second*4)
}
/*
output
write done: len= 4
*/
无缓冲写
无缓冲写需要注意的点是:必须先有协程等着读,写才能生效,否则 deadlock,下面是例子
package main
import (
"fmt"
"time"
)
func main() {
var ch3 = make(chan int)
// 如果再这里进行先往无缓冲chan写会导致block,所以先注掉
// fatal error: all goroutines are asleep - deadlock!
// ch3 <- 2
go func() {
for {
fmt.Println("start reading")
fmt.Println(<-ch3)
fmt.Println("read done")
}
}()
// 在这里写是正常的(最好先稍微sleep一下,以免上面的协程并没起来已经执行写了)
ch3 <- 2
time.Sleep(time.Second * 4)
}
无缓冲读
无缓冲读,当没有协程往里写的时候读会阻塞
代码详见无缓冲写
nil chan读写
读写未初始化的 chan 都会阻塞(deadlock)
为什么是这样呢,源码的chanrecv和chansend()函数都有下面这几行代码?
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
...
if c == nil {
if !block {
return
}
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
...
}
package main
import (
"fmt"
"time"
)
func main() {
var ch3 chan int
fmt.Println(<-ch3)
ch3<-1
time.Sleep(time.Second * 4)
}
/*
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]:
main.main()
/home/xd/code/demo/chan.go:8 +0x2d
exit status 2
*/
关闭chan
有(无)缓冲管道
关闭有无缓冲的chan都是常规操作,没什么
往已经关闭的chan内写数据会panic这算是常识了,就不说了(错误提示:panic: send on closed channel
)
只是不能重复关闭,重复关闭chan 会引发panic
package main
func main() {
var ch1 = make(chan int, 1)
close(ch1)
close(ch1)
}
/*
panic: close of closed channel
goroutine 1 [running]:
main.main()
/home/xd/code/demo/chan.go:5 +0x39
exit status 2
*/
关闭nil chan
关闭未初始化(nil)的chan会引发panic
package main
func main() {
var ch2 chan int
close(ch2)
}
/*
panic: close of nil channel
goroutine 1 [running]:
main.main()
/home/xd/code/demo/chan.go:6 +0x31
exit status 2
*/
读已经关闭的chan
这里肯定是读取的是有缓冲或者无缓冲的chan,因为nil chan 你close的时候就panic了
可以一直读到值,无论里面是不是有数据
有数据:读取现有数据,读完读对应类型的空值
没有数据:直接读取对应类型的空值
怎么知道chan 有没有关闭,关闭以后只读有效数据怎么实现
package main
import (
"fmt"
"time"
)
func main() {
var ch2 = make(chan int,5)
ch2 <-2
ch2 <-2
close(ch2)
go func() {
for {
// ok 表示只有当channel无数据,且channel被close了,才会返回ok=false。
// 目前如果chan里面有数据的情况下没有办法知道这个chan是不是关闭的。
// 这就是为什么避免出现异常,要在生产者一端关闭chan的原因
// 默认生产者关闭了以后肯定不会再写了。
val ,ok := <-ch2
if ok == true {
fmt.Println(val)
} else {
break
}
}
}()
time.Sleep(time.Second)
}
/*
output
xd@xd:~/code/demo$ go run chan.go
2
2
chan is empty and closed
*/