likes
comments
collection
share

Golang中的 channel 简单吗?

作者站长头像
站长
· 阅读数 18

引文

我对他说,走过去吧,那里树叶会向你招手,石头会向你微笑,河水会向你问候。那里没有贫贱也没有富贵,没有悲伤也没有疼痛,没有仇也没有恨......那里人人死而平等。 —— 《第七天》

前言

Golang 中谈起 channel,就不可能避免说到并发编程,在 Golang 中谈到并发编程,那不得不提起 另外一个名字 goroutineschannelgoroutines 就是 Golang 中并发编程的核心。Go中并发编程方式是将共享值在通道上传递,不由单独的执行线程主动共享,在任何给定的时间只有一个 goroutine 可以访问该值。在 Golang 中将此简言之不通过共享内存进行通信,要通过通信共享内存

channel 是什么?

Go语言中的 channel 是一种特殊的类型,遵循先入先出(First In First Out)的规则,保证收发数据的有序性。从字面意思上看,大概就是管道的意思。可以想象 channel 是一个传送管道,将 goroutine 想象成管道两边的人,一个从一边将物品放入管道,另一个人在另一边取出。图解示意:

Golang中的 channel 简单吗?

channel 的简单使用

如 map 一样,channel 也是使用 make 关键字来创建充当对底层数据结构的引用。

ch1 := make(chan struct{}) // 无缓冲 channel
ch2 := make(chan struct{},0) // 无缓冲 channel
ch3 := make(chan struct{}, 10) // 带缓冲 channel

在 make 创建时有一个可选的整数参数,它会设置 channel 的缓冲区大小。对于无缓冲或同步通道,默认值为0。

在这里,channel 又被分为了 unbuffered channelbuffered channel,也就是无缓冲区channel和带缓冲区channel。

unbuffered channel & buffered channel

unbuffered channel 就是缓冲大小为 0 的 channel,无缓冲区的 channel 本身是不存放数据的,在发送和接收都会被阻塞。也就是相当于,你现在是一个 send 身份,但是当另外一个没有 receive 你发送的值之前,你一直处于阻塞(等待接收)状态;反过来说,你现在是一个 receive 身份,你就会一直阻塞(等待发送)状态,在你拿到值之前,你会一直等待。

无缓冲区通道

来看下面这段代码:

func main() {
    ch := make(chan struct{})
    ch <- struct{}{}
    fmt.Println("send a struct")
}

这段代码执行时候会出现一个 deadlock 错误,死锁!为什么呢?

其实这里是因为 ch 是一个无缓冲区的 channel,unbuffered channel 只有在有 接受方 的时候才能发送成功。限制就好比,你伸手递出去一个物品,但是永远没有人从你手中接收这个物品。

所以想解决这个问题,只需要提供一个 接收者 就行了,修改代码为:

func main() {
    ch := make(chan struct{})

    go func() {
        defer close(ch)
        v := <-ch
        fmt.Printf("receive a struct: %v\n", v)
    }()
    
    ch <- struct{}{}
    fmt.Println("send a struct")
}

这时候数据发送成功,也能在另外一个 goroutine 里获取值,但是可以发现,这两个 goroutine 之间通信的数据信息是同步化的。因此,无缓冲通道也可以称为同步通道。

带缓冲区通道

先看代码:

func main() {
    ch := make(chan struct{}, 1)
    ch <- struct{}{}
    fmt.Println("send a struct")
}

在带缓冲区通道中,直接往 channel 中发送不超出大小的数据量,就不会出现 deadlock 错误。

那么我们对代码进行修改:

func main() {
    ch := make(chan struct{}, 1)
    ch <- struct{}{}
    ch <- struct{}{}
    fmt.Println("send a struct")
}

这时候,就会出现 deadlock 错误,因为在这个 channel 中缓冲大小为1,第一次发送没有被接收,就占满了channel的缓冲区,再对一个满的 channel 进行数据发送时,也会出现deadlock错误。

继续对代码进行修改:

func main() {
    ch := make(chan int, 1)
    go func() {
        defer close(ch)
        defer fmt.Println("我关闭了")
        ch <- 1
        ch <- 2
        fmt.Println("send a struct")
    }()

    time.Sleep(5 * time.Second)
    for {
        v, ok := <-ch
        if !ok {
            fmt.Println("channel closed")
            break
        }
        fmt.Printf("received a struct %s\n", v)
    }
}

它的执行结果是:

~ 等待5s
received a struct %!s(int=1)
received a struct %!s(int=2)
send a struct
我关闭了
channel closed

可以看到 我关闭了 在打印完接收后执行了,所以这个 ch 在缓冲区满后,被阻塞了,直到接收了第一个值,然后继续执行后面的代码。其实这里有个问题,如果我们将缓冲区设置大一点,他执行完后执行关闭了,接收值会有影响吗?

// 修改 
ch := make(chan int, 2)

此时,执行结果为:

send a struct
我关闭了
~ 等待5s
received a struct %!s(int=1)
received a struct %!s(int=2)
channel closed

这里可以直到,channel 关闭后,对 range 其实并不影响。

总结

  • 不通过共享内存进行通信,要通过通信共享内存
  • channel 分为 有缓冲区 和 无缓冲区的
  • 向一个满 channel 中发送数据会 deadlock,向一个空的 channel 中读取数据会 deadlock
  • 当 channel 被关闭后,依然可以读取缓冲区内的值

参考

转载自:https://juejin.cn/post/7231887884739346489
评论
请登录