英文:
Golang Channels dealing with send != receive
问题
考虑下面的示例代码。这段代码本身是完全正常工作的,但是当你交换标记为replace
的两行代码时,就会发生死锁。在发送和接收的数量不同时,有没有更好的方法来处理这种情况呢?
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // replace
a <- strconv.Itoa(i) + "a" // replace
}(i)
}
}()
a <- "0"
for i := 0; i < 2; i++ {
fmt.Println(<-b)
}
}
编辑:使用select
语句,有可能会选择到a
,但仍然无法防止死锁,因为goroutine无法执行。
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
c := make(chan bool)
cancel := make(chan bool)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // replace
a <- strconv.Itoa(i) + "a" // replace
c <- true
}(i)
}
}()
go func() {
<-c
<-c
cancel <- true
}()
a <- "0"
loop:
for {
select {
case ain := <-a:
fmt.Println("select", ain)
case bin := <-b:
fmt.Println("select", bin)
case <-cancel:
break loop
}
}
}
英文:
Consider the toy example below. The code works perfectly but when you interchange the 2 lines marked as replace
, there will be a deadlock. Is there a better way to deal with such a situation when you have different number of sends and receives?
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // replace
a <- strconv.Itoa(i) + "a" // replace
}(i)
}
}()
a <- "0"
for i := 0; i < 2; i++ {
fmt.Println(<-b)
}
}
EDIT: using a select statement, there's a chance that a
gets picked up by the select and there's still no way to prevent a deadlock because the goroutines can't execute
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
c := make(chan bool)
cancel := make(chan bool)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // replace
a <- strconv.Itoa(i) + "a" // replace
c <- true
}(i)
}
}()
go func() {
<-c
<-c
cancel <- true
}()
a <- "0"
loop:
for {
select {
case ain := <-a:
fmt.Println("select", ain)
case bin := <-b:
fmt.Println("select", bin)
case <-cancel:
break loop
}
}
}
答案1
得分: 2
使用select语句:
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // 替换
a <- strconv.Itoa(i) + "a" // 替换
}(i)
}
}()
// 不管哪个先到达,都会处理它
select {
case ain := <-a:
fmt.Println("发送了a", ain)
case bin := <-b:
fmt.Println("发送了b", bin)
case <-cancel:
break
}
}
这个示例会阻塞,直到a或b通道中有数据发送。
可选地,我通常会设置一个取消令牌或超时。
你原来的代码在switch语句中发生死锁,因为你在B通道上发送了数据,但你只在A通道上监听。在Golang中,你必须在发送数据之前就开始监听通道。这是处理多个通道的模式,因为你不知道哪个通道会先到达。
英文:
Use select:
package main
import "fmt"
import "strconv"
func main() {
a := make(chan string)
b := make(chan string)
go func() {
for i := 0; i < 2; i++ {
go func(i int) {
fmt.Println(<-a)
b <- strconv.Itoa(i) + "b" // replace
a <- strconv.Itoa(i) + "a" // replace
}(i)
}
}()
// regardless of which comes in first, this will handle it
select {
case ain <- a:
fmt.Println("sent a", ain)
case bin <- b:
fmt.Println("sent b", bin)
case <- cancel:
break
}
}
That example will sit and block for an item sent on either a or b channels.
Optionally I usually set a cancel token or a timeout.
Your original code deadlocks on the switch because you sent on B, where you were only listening on A. Golang requires you to be listening on the channel BEFORE you ever send to it. This is the pattern for multiple channels, not knowing which you are going to get first.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论