go并发日记·深入理解go语言channel与goroutine阻塞

锲而舍之,朽木不折;锲而不舍,金石可镂。

本文是自己对相关知识运用和理解的一些领悟,带着自己当初的疑惑疑问和大家分享交流,也欢迎大家留言分享自己的总结哦!

go中的通道就是channel,用于goroutine之间的通讯/数据的传递。可以想象成一个管,一端有人操作发送,一端有人操作接收,如果发送端没有准备好不能发送,即使接收端能正常接收那也不能拿到数据,发送端能正常发送但是接收端不能正常接收,那么整个流程也是不流通的。只有两端都能保证正常发送和接收,数据才能正常流通,你的想法才能实现。

定位:传递消息/数据(通知);使某个goroutine阻塞

goroutine可以理解为线程但比线程轻。以下每个goroutine可以看作一条线,比如main就是main线。

看下面的代码,main函数中只有main一个goroutine(main线程可以看作一个goroutine),由于ch是无缓冲通道,也就是不能暂存数据,只能传递,当main线执行ch <- 1时没有其他goroutine负责接收消息,因此发送者阻塞,main线挂起,此时下面的所有代码将不会执行

var ch = make(chan int)

func main() {
    ch <- 1
    //若干代码
}

所以它会提示:

fatal error: all goroutines are asleep - deadlock!

即没人来接收我要发的数据,我怎么给你发过去?所以我先睡了。

注意,以下两种情况是两码事:

1,发送端正常但接收端无goroutine

2,发送端与接收端goroutine都在运行,但接收端等不到数据(发送端并无数据发送)

此代码中只有一个goroutine, 所以向里面加数据或者存数据,都会锁死通道, 并且阻塞当前 goroutine, 也就是所有的goroutine(其实就main线一个)都在等待通道的开放(没人拿走数据通道是不会开放的),也就是死锁。关于go中通道死锁的具体情况分析传送门:go中通道的死锁分析

无缓冲通道  &  有缓冲通道

1,无缓冲的通道在取消息和存消息时都会挂起当前的goroutine,除非管的另一端已准备好

2,有缓冲通道:存消息时如果缓冲区大小大于等于存入的消息数量,那么不会阻塞,反之和无缓冲一样(挂起当前的goroutine)

3,从无缓冲通道取数据,必须要有数据流进来才可以,否则当前线阻塞;数据流入无缓冲通道, 如果没有其他goroutine来拿走这个数据,那么当前线阻塞

channel的底层实现是基于hchan结构体,结构体中的buf字段就是用来存放缓冲区的数据,它是一个环形队列,又环形链表实现。

看下面的代码,函数loop()被调用两次,两次的goroutine分别是:go关键字开启的goroutine和main线

func loop() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", i)
    }
}

func main() {
    go loop()
    loop()
}

结果是只输出一趟

main线退出的太快,他的loop执行完了但另一个goroutine还没来得及执行。

即要让你的groutine生效,最简单的办法是让main线程延迟退出,阻塞主线:

func main() {
    go loop()
    loop()
    time.Sleep(time.Second)
}

go中通道就扮演的是这样的角色!用通道怎么通知main线呢?请看下面的代码:

var complete chan int = make(chan int)

func loop() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", i)
    }

    complete <- 0   // 执行完毕了,发个消息,以0作为标志(此处就完美体现了channel的通知作用)
}


func main() {
    go loop()
    <- complete   // 如果取到消息了说明另一个goroutine跑完了. 否则main在此阻塞住
}

main函数的 <- complete 其实就是阻塞住main线不让main线过早跑完,直到main线从complete中读出消息(0)。

关于channel的控制并发实战可移步https://blog.csdn.net/HYZX_9987/article/details/101201408

 

 

你可能感兴趣的:(#,go)