golang:有缓存channel和无缓存channel的区别

梦里梦外; 2023-07-05 11:48 70阅读 0赞

Channel

  • 通道又叫channel,顾名思义,channel的作用就是在多线程之间传递数据的。
  • channel 是一种特殊的类型。在任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。
  • 通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序
  • channel 分为有缓冲和无缓冲两种。

创建无缓冲channel

  1. chreadandwrite :=make(chan int)
  2. chonlyread := make(<-chan int) //创建只读channel
  3. chonlywrite := make(chan<- int) //创建只写channel

创建有缓冲channel

  1. chreadandwrite :=make(chan int, 1)
  2. chonlyread := make(<-chan int) //创建只读channel
  3. chonlywrite := make(chan<- int) //创建只写channel

两者区别

下面我们来看一个例子:

  1. ch := make(chan int)
  2. ch <- 1
  3. go func() {
  4. <-ch
  5. fmt.Println("1")
  6. }()
  7. fmt.Println("2")

这段代码执行时会出现一个错误:fatal error: all goroutines are asleep - deadlock!

这个错误的意思是说线程陷入了死锁,程序无法继续往下执行。那么造成这种错误的原因是什么呢?

  • 我们创建了一个无缓冲的channel,然后给这个channel赋值了,程序就在赋值后陷入了死锁。
  • 因为我们的channel是无缓冲的,即同步的,赋值完成之后来不及读取channel,程序就已经阻塞了。
  • channel的机制是先进先出,如果你给channel赋值了,那么必须要读取它的值,不然就会造成阻塞,当然这个只对无缓冲的channel有效。对于有缓冲的channel,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

对于上面的例子有两种解决方案:
1、给channel增加缓冲区,然后在程序的最后让主线程休眠一秒,代码如下:

  1. ch :=make(chan int,1)
  2. ch <- 1
  3. go func() {
  4. v := <-ch
  5. fmt.Println(v)
  6. }()
  7. time.Sleep(1 * time.Second)
  8. fmt.Println("2")

这样的话程序就会依次打印出1、2

2、把ch<-1这一行代码放到子线程代码的后面,代码如下:

  1. ch :=make(chan int)
  2. go func() {
  3. v := <-ch
  4. fmt.Println(v)
  5. }()
  6. ch <- 1
  7. fmt.Println("2")
  8. /*
  9. 1
  10. 2
  11. */

这里就不用让主线程休眠了,因为channel在主线程中被赋值后,主线程就会阻塞,直到channel的值在子线程中被取出。

最后我们看一个生产者和消费者的例子:

  1. import (
  2. "fmt"
  3. "time"
  4. )
  5. func produce(p chan<- int) {
  6. for i := 0; i < 10; i++ {
  7. p <- i
  8. fmt.Println("send:", i)
  9. }
  10. }
  11. func consumer(c <-chan int) {
  12. for i := 0; i < 10; i++ {
  13. v := <-c
  14. fmt.Println("receive:", v)
  15. }
  16. }
  17. func main() {
  18. ch := make(chan int)
  19. go produce(ch)
  20. go consumer(ch)
  21. time.Sleep(1 * time.Second)
  22. }

在这段代码中,因为channel是没有缓冲的,所以当生产者给channel赋值后,生产者这个线程会阻塞,直到消费者线程将channel中的数据取出。消费者第一次将数据取出后,进行下一次循环时,消费者的线程也会阻塞,因为生产者还没有将数据存入,这时程序会去执行生产者的线程。程序就这样在消费者和生产者两个线程间不断切换,直到循环结束。
在这里插入图片描述
下面我们再看一个带缓冲的例子:

  1. func produce(p chan<- int) {
  2. for i := 0; i < 10; i++ {
  3. p <- i
  4. fmt.Println("send:", i)
  5. }
  6. }
  7. func consumer(c <-chan int) {
  8. for i := 0; i < 10; i++ {
  9. v := <-c
  10. fmt.Println("receive:", v)
  11. }
  12. }
  13. func main() {
  14. ch := make(chan int, 10)
  15. go produce(ch)
  16. go consumer(ch)
  17. time.Sleep(1 * time.Second)
  18. }

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,70人围观)

还没有评论,来说两句吧...

相关阅读

    相关 golangchannel超时

    基本语法 通道声明和初始化 我们可以通过`chan`关键字来声明通道类型变量 var ch chan int // 声明一个通道类型变量 ch,并且通道

    相关 golang channel 使用

    本文对channel使用中的几个疑惑,以例子的形式加以说明。 普通channel 缺省情况下,发送和接收会一直阻塞着,直到另一方准备好. 例如: pack

    相关 golangChannel

    Channel是Go中的一个核心类型,可以将其看成一个管道,通过它并发单元就可以发送或者接收数据进行通信(communication)。 `Do not communicat