持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
1、背景
Golang在并发编程上有两大利器,分别是channel和goroutine,这篇文章我们先聊聊channel。熟悉Golang的人都知道一句名言:“Don’t communicate by sharing memory, share memory by communicating.”。这句话有两层意思,Go语言确实在sync包中提供了传统的锁机制,但更推荐使用channel来解决并发问题。这篇文章会先从channel的用法、channel的原理两部分对channel做一个较为深入的探究 什么是channel
2、什么是channel
从字面上看,channel的意思大概就是管道的意思。channel是一种go协程用以接收或发送消息的安全的消息队列,channel就像两个go协程之间的导管,来实现各种资源的同步。
2.1、以下图片示意channel的用法
2.2、channel的一般用法
- channel的创建
// 创建一个类型为int,无缓冲的channel
ch := make(chan int)
/ 缓冲为1 的channel
ch := make(chan int, 1)
- 发送消息
// 将2发送到ch
ch <- 2
- 接收消息
// n接收从ch发出的值
n := <- ch
// 判断是否close
n, ok := <- ch
if ok {
fmt.Println(n) // 2
}
- 关闭管道
close(ch)
- 阻塞式遍历channel :for …range
ch := make(chan int)
// 阻塞模式
for i := range ch {
fmt.Println(i)
}
- 非阻塞式遍历channel select
ch := make(chan int)
select {
case res := <-ch:
fmt.Println(res)
default :
fmt.Println("无数据")
}
- 可以判断超时时间的channel
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
2.3、channel使用可能会遇到的问题
fatal error: all goroutines are asleep - deadlock!
- 死锁1:nil channel 的读写
- 死锁2:goroutine开启之前使用channel
- 死锁3 :channel1中调用了channel2,channel2中调用channel1
- 死锁4:直接读取空channel的死锁
- 死锁5:超过channel缓冲区继续写入数据导致死锁
- 死锁6:for range 使用不当导致的死锁
死锁1:nil channel 的读写:
func main() {
// 空channel
var ch chan struct{}
<-ch
}
死锁2:goroutine开启之前使用channel
func main() {
ch := make(chan int)
ch <- 100 //此处死锁
go func() {
num := <-ch
fmt.Println("num=", num)
}()
//ch <- 100 此处不死锁
time.Sleep(time.Second*3)
fmt.Println("finish")
}
死锁3 :channel1中调用了channel2,channel2中调用channel1
func main() {
ch1 := make(chan int )
ch2 := make(chan int )
go func() {
for {
select {
case num := <-ch1:
fmt.Println("num=", num)
ch2 <- 100
}
}
}()
for {
select {
case num := <-ch2:
fmt.Println("num=", num)
ch1 <- 300
}
}
}
死锁4:直接读或写 无缓冲channel
func main() {
ch := make(chan int)
//close(ch) 向关闭的channel中读取数据 是该数据的类型的零值
num := <-ch
ch <- 1
fmt.Println("num=", num)
}
死锁5:超过channel缓冲区继续写入数据导致死锁
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 3
num := <-ch
fmt.Println("num=", num)
}
死锁7:for range 使用不当导致的死锁
以下6中案例可以自己猜测运行结果,是否和自己所想一致
func main() {
// range 1
pipline := make(chan string)
go func() {
pipline <- "hello world"
pipline <- "hello China"
}()
for data := range pipline{
fmt.Println(data)
}
// range 2
pipline := make(chan string)
go func() {
pipline <- "hello world"
pipline <- "hello China"
close(pipline)
}()
for data := range pipline{
fmt.Println(data)
}
// range 3
pipline := make(chan string)
go func() {
pipline <- "hello world"
pipline <- "hello China"
}()
go func() {
for data := range pipline{
fmt.Println(data)
}
fmt.Println("你能看到我吗")
}()
time.Sleep(time.Second)
fmt.Println("end")
// range 4
pipline := make(chan string)
go func() {
pipline <- "hello world"
pipline <- "hello China"
}()
go func() {
for data := range pipline {
fmt.Println(data)
}
fmt.Println("你能看到我吗")
}()
fmt.Println("end")
select {}
// range 5
pipline := make(chan string)
go func() {
for true{
time.Sleep(time.Second)
}
}()
go func() {
pipline <- "hello world"
pipline <- "hello China"
}()
go func() {
for data := range pipline {
fmt.Println(data)
}
fmt.Println("你能看到我吗")
}()
select {}
// range 6
pipline := make(chan string)
go func() {
pipline <- "hello world"
pipline <- "hello China"
close(pipline)
}()
go func() {
for data := range pipline{
fmt.Println(data)
}
fmt.Println("你能看到我吗")
}()
time.Sleep(time.Second)
fmt.Println("end")
}
通过以上案例可以说明:
- 向一个nil channel读写消息 panic
- 向无缓存的channel发送数据,send 与recv 必须处于2个G 中
- channel关闭后不可以继续向channel发送消息,但可以继续从channel接收消息;
- 当channel关闭并且缓冲区为空时,继续从从channel接收消息会得到一个对应类型的零值