将通道从无缓冲更改为有缓冲可以防止 goroutine 运行。

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

Changing channel from unbuffered to buffered prevents goroutine from running

问题

这是一个使用通道和选择语句的goroutine练习。如果将disconnect通道从无缓冲通道更改为有缓冲通道,goroutine将根本不会运行。

为什么从无缓冲通道更改为有缓冲通道会阻止goroutine的运行?

func SelectDemo(wg *sync.WaitGroup) {

    messageCh := make(chan int, 10)
    disconnectCh := make(chan struct{})
    // 如果通道是有缓冲的,goroutine将不会运行
    //disconnectCh := make(chan struct{}, 1)

    defer close(messageCh)
    defer close(disconnectCh)
    go func() {
        fmt.Println("  goroutine")
        wg.Add(1)
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:
                fmt.Println("  disconnectCh")
                // 在退出之前清空有缓冲的通道
                for {
                    select {
                    case v := <-messageCh:
                        fmt.Println(v)
                    default:
                        fmt.Println("  disconnection, return")
                        wg.Done()
                        return
                    }
                }
            }
        }
    }()

    fmt.Println("Sending ints")
    for i := 0; i < 10; i++ {
        messageCh <- i
    }

    fmt.Println("Sending done")
    disconnectCh <- struct{}{}
}

以下是从主函数调用该函数的代码。我使用等待组来确保在程序退出之前goroutine完成:

wg := sync.WaitGroup{}
SelectDemo(&wg)
wg.Wait()
英文:

Here is an exercise using channels and select in a goroutine. If the disconnect channel is changed to a buffered channel the goroutine doesn't run at all.

Why does changing from an unbuffered to a buffered channel prevent running the goroutine?

func SelectDemo(wg *sync.WaitGroup) {

	messageCh := make(chan int, 10)
	disconnectCh := make(chan struct{})
	//	go routine won&#39;t run if channel is buffered
	//disconnectCh := make(chan struct{}, 1)

	defer close(messageCh)
	defer close(disconnectCh)
	go func() {
		fmt.Println(&quot;  goroutine&quot;)
		wg.Add(1)
		for {
			select {
			case v := &lt;-messageCh:
				fmt.Println(v)
			case &lt;-disconnectCh:
				fmt.Println(&quot;  disconnectCh&quot;)
                //  empty the buffered channel before exiting
				for {
					select {
					case v := &lt;-messageCh:
						fmt.Println(v)
					default:
						fmt.Println(&quot;  disconnection, return&quot;)
						wg.Done()
						return
					}
				}
			}
		}
	}()

	fmt.Println(&quot;Sending ints&quot;)
	for i := 0; i &lt; 10; i++ {
		messageCh &lt;- i
	}

	fmt.Println(&quot;Sending done&quot;)
	disconnectCh &lt;- struct{}{}
}

Here's the code to call the function from main. I use the wait group to assure that the goroutine completes before the program exits:

wg := sync.WaitGroup{}
ch09.SelectDemo(&amp;wg)
wg.Wait()

答案1

得分: 3

这段代码逻辑有很多缺陷,其中一些包括:

  1. 由于messageCh是有缓冲的,所以这段代码不会阻塞:
for i := 0; i < 10; i++ {
    messageCh <- i
}

因此,下一段代码会在快速路径上运行:

disconnectCh <- struct{}{}

如果你将disconnectCh也设置为有缓冲,那么这行代码也会在没有阻塞的情况下运行,并且在运行wg.Add(1)之前,SelectDemo函数可能已经退出。

所以:你必须将

wg.Add(1)

放在

go func() {

之前。

  1. 即使在go func() {之前加上了wg.Add(1),你还有:
defer close(messageCh)
defer close(disconnectCh)

这将在SelectDemo函数返回时关闭两个通道。而且由于两个通道都准备好了,这个select是一个随机选择:

fmt.Println("  goroutine")
for {
    select {
    case v := <-messageCh:
        fmt.Println(v)
    case <-disconnectCh:

很有可能第二个select会一直运行下去,因为messageCh被关闭后,通道数据读取后会永远返回0

case v := <-messageCh:
    fmt.Println(v)
英文:

That code logic has many flaws - some of them are:
1- Since the messageCh is buffered, this code is not blocking:

	for i := 0; i &lt; 10; i++ {
		messageCh &lt;- i
	}

so the next code is in the fast path to run:

disconnectCh &lt;- struct{}{}

if you make the disconnectCh buffered, this line runs without blocking too, and the SelectDemo function may exit befor running the wg.Add(1).

So: You must put:

wg.Add(1)

before

go func() {

2- Even with wg.Add(1) before go func() { code -
you have:

	defer close(messageCh)
	defer close(disconnectCh)

which will close both channels at SelectDemo function return
And this select is a random selection since both channels are ready:

fmt.Println(&quot;  goroutine&quot;)
		for {
			select {
			case v := &lt;-messageCh:
				fmt.Println(v)
			case &lt;-disconnectCh:

and it is highly likely that the second select:

				for {
					select {
					case v := &lt;-messageCh:
						fmt.Println(v)
					default:
						fmt.Println(&quot;  disconnection, return&quot;)
						wg.Done()
						return
					}
				}

will run forever since the messageCh is closed, returning 0 forever after channel data read:

case v := &lt;-messageCh:
	fmt.Println(v)

答案2

得分: -1

程序执行速度很快

访问网址:https://pkg.go.dev/sync#WaitGroup.Add

请注意,在计数器为零时发生的具有正增量的调用必须在等待之前发生。具有负增量的调用,或者在计数器大于零时开始的具有正增量的调用,可以在任何时间发生。通常,这意味着对Add的调用应该在创建goroutine或其他等待事件的语句之前执行。如果一个WaitGroup被重用来等待几组独立的事件,新的Add调用必须在所有先前的Wait调用返回之后发生。请参考WaitGroup示例。

func SelectDemo(wg *sync.WaitGroup) {

	messageCh := make(chan int, 10)
	disconnectCh := make(chan struct{}, 1)
	// 如果通道是有缓冲的,goroutine将不会运行
	//disconnectCh := make(chan struct{}, 1)

	wg.Add(1)

	defer close(messageCh)
	defer close(disconnectCh)
	go func() {
		fmt.Println("  goroutine")
		for {
			select {
			case v := <-messageCh:
				fmt.Println(v)
			case <-disconnectCh:
				fmt.Println("  disconnectCh")
				// 在退出之前清空缓冲通道

				fmt.Println("  disconnection, return")
				wg.Done()
				return
			}
		}
	}()

	fmt.Println("Sending ints")
	for i := 0; i < 10; i++ {
		messageCh <- i
	}

	// 延迟发送退出信号
	time.Sleep(3 * time.Second)

	fmt.Println("Sending done")
	disconnectCh <- struct{}{}
}

我修改了你的代码

再试一次!

英文:

Program execution is fast

Visit URL: https://pkg.go.dev/sync#WaitGroup.Add

Note that calls with a positive delta that occur when the counter is zero must happen before a Wait. Calls with a negative delta, or calls with a positive delta that start when the counter is greater than zero, may happen at any time. Typically this means the calls to Add should execute before the statement creating the goroutine or other event to be waited for. If a WaitGroup is reused to wait for several independent sets of events, new Add calls must happen after all previous Wait calls have returned. See the WaitGroup example.

func SelectDemo(wg *sync.WaitGroup) {

	messageCh := make(chan int, 10)
	disconnectCh := make(chan struct{}, 1)
	//  go routine won&#39;t run if channel is buffered
	//disconnectCh := make(chan struct{}, 1)

	wg.Add(1)

	defer close(messageCh)
	defer close(disconnectCh)
	go func() {
		fmt.Println(&quot;  goroutine&quot;)
		for {
			select {
			case v := &lt;-messageCh:
				fmt.Println(v)
			case &lt;-disconnectCh:
				fmt.Println(&quot;  disconnectCh&quot;)
				//  empty the buffered channel before exiting

				fmt.Println(&quot;  disconnection, return&quot;)
				wg.Done()
				return
			}
		}
	}()

	fmt.Println(&quot;Sending ints&quot;)
	for i := 0; i &lt; 10; i++ {
		messageCh &lt;- i
	}

	//Delay sending exit signal
	time.Sleep(3 * time.Second)

	fmt.Println(&quot;Sending done&quot;)
	disconnectCh &lt;- struct{}{}
}

I modified your code

try again!!!

huangapple
  • 本文由 发表于 2022年2月11日 14:05:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/71075804.html
匿名

发表评论

匿名网友

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

确定