0

我是 golang 的新手,正在尝试使用 goroutines 试验缓冲通道。我以为我理解了缓冲通道是如何与 goroutine 一起工作的,直到遇到下面的例子,这对我来说是一个脑筋急转弯,并且对我迄今为止所学的概念给予了极大的帮助。

这是我从文章https://medium.com/rungo/anatomy-of-channels-in-go-concurrency-in-go-1ec336086adb中获取的原始示例。

代码#1:(通道容量=3,通道长度=3,环路长度=4)

func squares(c chan int) {
    for i := 0; i <= 3; i++ {
        num := <-c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)

    go squares(c)

    c <- 1
    c <- 2
    c <- 3
    
    fmt.Println("main() stopped")
}

输出:

main() started
main() stopped

解释:在上面的程序中,通道 c 的缓冲容量为 3。也就是说它可以容纳 3 个值。由于缓冲区没有溢出(因为我们没有推送任何新值),主 goroutine 不会阻塞并且程序存在。我已经理解了这个例子。

代码#2:(通道容量=3,通道长度=4,环路长度=4)

func squares(c chan int) {
    for i := 0; i <= 3; i++ {
        num := <-c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)

    go squares(c)

    c <- 1
    c <- 2
    c <- 3
    c <- 4 // goroutine blocks here
    
    fmt.Println("main() stopped")
}

输出:

main() started
1
4
9
16
main() stopped

解释:由于现在填充的缓冲区通过 c <- 4 发送操作获得推送,主 goroutine 块和正方形 goroutine 排出所有值。这也是我理解的。

代码#3:(通道容量=3,通道长度=5,环路长度=5)

func squares(c chan int) {
    for i := 0; i <= 4; i++ {
        num := <-c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)

    go squares(c)

    c <- 1
    c <- 2
    c <- 3
    c <- 4 // goroutine blocks here
    c <- 5

    fmt.Println("main() stopped")
}

输出:

main() started
1
4
9
16
25
main() stopped

说明:我为通道添加了另一个值,即 5。虽然通道容量只有 3。

我知道在通道接收到 n+1 个发送操作之前,它不会阻塞当前的 goroutine。在值 4 上,它接收 n+1 操作,这就是 goroutine 被阻塞并耗尽所有值的原因,但我无法理解的是通道如何处理 n+2 操作。是不是因为我们已经从通道中读取了值,并且我们有更多的阅读空间?

4

2 回答 2

4

此处的通道容量未满,因为您的squaresgoroutine 正在运行,并且它会立即接收发送到通道的值。

但我无法理解的是通道如何处理 n+2 操作。

在 n+1 发送操作时,通道容量已满,因此将阻塞。从通道接收到至少一个值后(因此有空间可用于发送下一个值)n+1 发送操作继续,容量再次满。现在在 n+2 发送操作时,由于容量已满,因此它将阻塞,直到从通道接收到至少一个值,依此类推。

于 2021-07-19T09:15:46.620 回答
0

您观察到调度程序以某种方式对您的 porgram 的操作进行排序,但是您显示的代码并不能保证您的指令将始终以这种方式执行。

你可以尝试运行你的程序 100 次,看看你是否总是有相同的输出:

go build myprogram
for i in {1..100}; do
  ./myprogram
done

您还可以打开竞赛检测器(竞赛检测器的一个效果是它在调度程序中引入了更多随机化):

go build -race myprogram
for i in {1..100}; do
  ./myprogram
done

以下是一些与您最后的“5 项”示例兼容的输出:

main() started
1
4
9
16
main() stopped
main() started
1
4
main() stopped
9
main() started
1
main() stopped

为了更具体地了解可以使什么具有“始终相同的行为”,这里有一些非常标准的方法可以让您的示例程序在退出之前运行其所有任务:

  • 使用sync.WaitGroup, 让squares()函数表明它已经完成了它的工作:
func squares(c chan int, wg *sync.WaitGroup) {
    defer wg.Done()  // <- decrement the counter by 1 when
                     //    returning from this function

    for i := 0; i <= 3; i++ {
        num := <-c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)
    var wg sync.WaitGroup
    
    wg.Add(1)   // <- increment the waitgroup counter
    go squares(c, &wg)

    c <- 1
    c <- 2
    c <- 3
    c <- 4
   
    wg.Wait() // <- wait for the counter to go back to 0
 
    fmt.Println("main() stopped")
}
  • in main():完成输入值后关闭通道,
    in squaresrange在通道上使用以获取所有要处理的值
// you can tell the compiler "this channel will be only used as a receiver"
func squares(c <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()

    // consume all values until the channel is closed :
    for num := range c {
        fmt.Println(num * num)
    }
}

func main() {
   ...

   c <- 1
   c <- 2
   c <- 3
   c <- 4
   c <- 5
   c <- 6
   c <- 7
   close(c)  // close the channel to signal "no more values"

   wg.Wait()

   ...
}

通过上述修改,您的程序将始终在退出之前在标准输出上打印其所有值。

游乐场: https: //play.golang.org/p/qD_FHCpiub7

于 2021-07-19T09:22:19.873 回答