我是 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 个操作。是不是因为我们已经从通道中读取了值,并且我们有更多的读取空间?
答案 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)
通过上述修改,您的程序将始终在退出前在标准输出上打印其所有值。