使用 For 循环的 Goroutines 缓冲通道

时间:2021-07-19 08:50:30

标签: go concurrency channel goroutine buffered

我是 golang 的新手,正在尝试使用 goroutines 试验缓冲通道。我以为我了解缓冲通道如何与 goroutines 一起工作,直到遇到下面的示例,这对我来说是一个脑筋急转弯,并让我迄今为止学到的概念大放异彩。

这是我从文章 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 个操作。是不是因为我们已经从通道中读取了值,并且我们有更多的读取空间?

2 个答案:

答案 0 :(得分:4)

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

<块引用>

但我无法理解的是 n+2 操作是如何进行的 按渠道处理。

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

答案 1 :(得分:0)

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

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

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

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

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

以下是一些输出,它们也与您上一个“5 项”示例兼容:

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") } 函数指示它已完成其工作:
main()
  • squares 中:完成输入值后关闭通道,
    range 中:在通道上使用 // 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() ... } 来获取要处理的所有值
result = []
for rna in RNA_list:
    protein_seq = ""
    for i in range(0, len(rna),3):
           codon=rna[i:i+3]
           protein_seq += RNA_codon[codon]
    result.append(protein_seq)

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

游乐场:source code [GitHub]

相关问题