Golang通道处理发送不等于接收的情况

huangapple go评论93阅读模式
英文:

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 &quot;fmt&quot;
import &quot;strconv&quot;

func main() {
	a := make(chan string)
	b := make(chan string)

	go func() {
		for i := 0; i &lt; 2; i++ {
			go func(i int) {
				fmt.Println(&lt;-a)
				b &lt;- strconv.Itoa(i) + &quot;b&quot; // replace
				a &lt;- strconv.Itoa(i) + &quot;a&quot; // replace
			}(i)
		}
	}()

	a &lt;- &quot;0&quot;
	for i := 0; i &lt; 2; i++ {
		fmt.Println(&lt;-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 &quot;fmt&quot;
import &quot;strconv&quot;

func main() {
	a := make(chan string)
	b := make(chan string)
	c := make(chan bool)
	cancel := make(chan bool)

	go func() {
		for i := 0; i &lt; 2; i++ {
			go func(i int) {
				fmt.Println(&lt;-a)
				b &lt;- strconv.Itoa(i) + &quot;b&quot; // replace
				a &lt;- strconv.Itoa(i) + &quot;a&quot; // replace
				c &lt;- true
			}(i)
		}
	}()

	go func() {
		&lt;-c
		&lt;-c
		cancel &lt;- true
	}()

	a &lt;- &quot;0&quot;

loop:
	for {
		select {
		case ain := &lt;-a:
			fmt.Println(&quot;select&quot;, ain)
		case bin := &lt;-b:
			fmt.Println(&quot;select&quot;, bin)
		case &lt;-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 &quot;fmt&quot;
import &quot;strconv&quot;

func main() {
	a := make(chan string)
	b := make(chan string)

	go func() {
		for i := 0; i &lt; 2; i++ {
			go func(i int) {
				fmt.Println(&lt;-a)
				b &lt;- strconv.Itoa(i) + &quot;b&quot; // replace
				a &lt;- strconv.Itoa(i) + &quot;a&quot; // replace
			}(i)
		}
	}()

    // regardless of which comes in first, this will handle it 
    select {
    case ain &lt;- a:
        fmt.Println(&quot;sent a&quot;, ain)
    case bin &lt;- b:
        fmt.Println(&quot;sent b&quot;, bin)
    case &lt;- 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.

huangapple
  • 本文由 发表于 2017年6月1日 19:59:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/44306955.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定