Go无缓冲通道(同步通道)

发布于:2024-11-27 ⋅ 阅读:(10) ⋅ 点赞:(0)

无缓冲的通道又称为阻塞的通道,我们来看一下如下代码片段。

package main

import "fmt"

func main() {
    ch := make(chan int)
    ch <- 1
    fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现以下错误:
在这里插入图片描述

deadlock表示我们程序中的 goroutine 都被挂起导致程序死锁了。为什么会出现deadlock错误呢?

因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段

上面的代码会阻塞在ch <- 1这一行代码形成死锁,那如何解决这个问题呢?

其中一种可行的方法是创建一个 goroutine 去接收值,例如:

package main

import (
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    ch := make(chan int)
    ch <- 1
    go func() {
       defer wg.Done()
        v := <-ch
       fmt.Println(v)
    }()
    close(ch)
    wg.Wait()
}

在这里插入图片描述

我们已经开了一个go协程从管道中读去数据了,为什么还会报错呢?

因为当程序执行到 ch <- 1时,进已经发生了阻塞,下面的go协程还没有来得及启动。
go的channel在执行写入时会先检查在此之前有没有读取数据的一方已经存在,在读取时会先检查在此之前有没有写入方已经存在。

当我们将读的协程先启动,再写入,就可以了,代码如下:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    ch := make(chan int)
    go func() {
       defer wg.Done()
       v := <-ch
       fmt.Println(v)
    }()
    ch <- 1
    close(ch)
    wg.Wait()
}

在这里插入图片描述

同理,如果对一个无缓冲通道执行接收操作时,没有任何向通道中发送值的操作那么也会导致接收操作阻塞

package main

import "fmt"

func main() {
    ch := make(chan int)
    <-ch
    fmt.Println("接收成功")
}

在这里插入图片描述

其中一种可行的方法是创建一个 goroutine 去写入值,例如:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    ch := make(chan int)
    v := <-ch
    go func() {
       defer wg.Done()
       ch <- 1
    }()
    fmt.Println(v)
    close(ch)
    wg.Wait()
}

在这里插入图片描述

同理,因为当程序执行到 v := <-ch 时,进已经发生了阻塞,下面的go协程还没有来得及启动。
当我们将写的协程先启动,再读取,就可以了,代码如下:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    ch := make(chan int)
    go func() {
       defer wg.Done()
       ch <- 1
    }()
    v := <-ch
    fmt.Println(v)
    close(ch)
    wg.Wait()
}

在这里插入图片描述

使用无缓冲通道进行通信将导致发送和接收的 goroutine 同步化。因此,无缓冲通道也被称为同步通道
同步:
两个goroutine1(写入方)、goroutine2(读取方),
goroutine1先执行,如果想要再次发送(写入)数据的话,必须等待goroutine2将channel中的数据取出来(读取)之后,才能再次发送。
goroutine2先执行,如果想要再次接收数据的话,必须等待goroutine1再次向channel中写入数据之后,才能再次接收。
执行顺序(goroutine1,goroutine2,goroutine1,goroutine2…)goroutine1和goroutine2交替执行。
示例演示:
使用一个无缓冲channel和两个goroutine实现交替打印一个字符串。

package main

import "fmt"

func main() {
    ch := make(chan int)

    str := "hello, world"

    go func() {
       for {
          index, ok := <-ch
          if !ok {
             break
          }
          if index >= len(str) {
             close(ch)
             break
          }
          fmt.Printf("Goroutine1 : %c\n", str[index])
          ch <- index + 1
       }
    }()

    ch <- 0

    for {
       index, ok := <-ch
       if !ok {
          break
       }
       if index >= len(str) {
          close(ch)
          break
       }
       fmt.Printf("Goroutine1 : %c\n", str[index])
       ch <- index + 1
    }

}

在这里插入图片描述