简体   繁体   English

Golang 中的 select 陷入无限循环

[英]Stuck in infinite loop in for select in Golang

The code given below is the sample code for my use case.下面给出的代码是我的用例的示例代码。 I want to read data from ch1 and ch2 but got stuck into infinite loop.我想从ch1ch2读取数据,但陷入了无限循环。

package main
import "fmt"

func main() {
    ch1, ch2 := func() (<-chan int, <-chan int) {
        ch_1 := make(chan int)
        ch_2 := make(chan int)

        go worker_1(ch_1, ch_2)
        go worker_2(ch_1, ch_2)

        return ch_1, ch_2
    }()

    // trying to read this way but it is not working
    for {
        select {
        case a := <-ch1:
            fmt.Println("from ch1", a)
        case a := <-ch2:
            fmt.Println("from ch2", a)
        default:
            fmt.Println("done")
        }
    }
}

func worker_1(ch1, ch2 chan int) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

func worker_2(ch1, ch2 chan int) {
    for i := 101; i < 200; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

Close the channels when the workers are done.工人完成后关闭通道。 Break out of the receive loop after both channels are closed.在两个通道都关闭后跳出接收循环。

package main

import (
    "fmt"
    "sync"
)

func main() {
    ch1, ch2 := func() (<-chan int, <-chan int) {
        ch_1 := make(chan int)
        ch_2 := make(chan int)

        var wg sync.WaitGroup
        wg.Add(2)

        go worker_1(&wg, ch_1, ch_2)
        go worker_2(&wg, ch_1, ch_2)

        // Close channels after goroutiens complete.
        go func() {
            wg.Wait()
            close(ch_1)
            close(ch_2)
        }()

        return ch_1, ch_2
    }()

    // While we still have open channels ...
    for ch1 != nil || ch2 != nil {
        select {
        case a, ok := <-ch1:
            if ok {
                fmt.Println("from ch1", a)
            } else {
                // note that channel is closed.
                ch1 = nil
            }
        case a, ok := <-ch2:
            if ok {
                fmt.Println("from ch2", a)
            } else {
                // note that channel is closed.
                ch2 = nil
            }
        }
    }
}

func worker_1(wg *sync.WaitGroup, ch1, ch2 chan int) {
    defer wg.Done()
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

func worker_2(wg *sync.WaitGroup, ch1, ch2 chan int) {
    defer wg.Done()
    for i := 101; i < 200; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

Here is one solution:这是一种解决方案:

package main

import (
    "fmt"
    "sync"
)

func main() {
    // Create channels
    ch1, ch2 := make(chan int), make(chan int)

    // Create workers waitgroup with a counter of 2
    wgWorkers := sync.WaitGroup{}
    wgWorkers.Add(2)

    // Run workers
    go worker(&wgWorkers, ch1, ch2, 0, 100)   // Worker 1
    go worker(&wgWorkers, ch1, ch2, 101, 200) // Worker 2

    // Create readers waitgroup with a counter of 2
    wgReader := sync.WaitGroup{}
    wgReader.Add(2)

    // Run readers
    go reader(&wgReader, ch1, 1) // Reader 1
    go reader(&wgReader, ch2, 2) // Reader 2

    // Wait for workers to finish
    wgWorkers.Wait()

    // Close workers channels
    close(ch1) // Makes reader 1 exit after processing the last element in the channel
    close(ch2) // Makes reader 2 exit after processing the last element in the channel

    // Wait for both readers to finish processing before exiting the program
    wgReader.Wait()
}

// Worker function definition
func worker(wg *sync.WaitGroup, ch1, ch2 chan<- int, from, to int) {
    // Decrement reader waitgroup counter by one when function returns
    defer wg.Done()
    for i := from; i < to; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

// Reader function definition
func reader(wg *sync.WaitGroup, ch <-chan int, chNum int) {
    // Decrement reader waitgroup counter by one when function returns
    defer wg.Done()
    // Here we iterate on the channel fed by worker 1 or worker 2.
    // for-range on a channel exits when the channel is closed.
    for i := range ch {
        fmt.Printf("from ch%d: %d\n", chNum, i)
    }
}

Explainations are in the code comments.解释在代码注释中。

The general idea using a channel is to spawn a routine and give it both responsibility to write and close the channel.使用通道的一般想法是产生一个例程并赋予它编写和关闭通道的责任。 So it is possible to detect the end of a channel using standard idioms.因此,可以使用标准习语来检测通道的结束。

https://go.dev/play/p/ssNsbxe2Ur- https://go.dev/play/p/ssNsbxe2Ur-

The language provides syntax to read a channel until closure该语言提供了读取通道直到关闭的语法

https://go.dev/ref/spec#For_statements https://go.dev/ref/spec#For_statements

For channels, the iteration values produced are the successive values sent on the channel until the channel is closed.对于通道,产生的迭代值是通道上发送的连续值,直到通道关闭。 If the channel is nil, the range expression blocks forever.如果通道为 nil,则范围表达式将永远阻塞。

https://go.dev/ref/spec#Receive_operator https://go.dev/ref/spec#Receive_operator

x, ok:= <-ch The value of ok is true if the value received was delivered by a successful send operation to the channel, or false if it is a zero value generated because the channel is closed and empty. x, ok:= <-ch如果接收到的值是通过成功的发送操作传递到通道的,则 ok 的值为 true,如果它是由于通道关闭且为空而生成的零值,则为 false。

The runtime may enter the default branch of a select anytime none of the other cases are ready to read or write.运行时可能会在没有其他情况准备好读取或写入时进入 select 的默认分支。 It might happen that a channel, or a group of channels, are not ready for a read if they are closed, but also if they are slow to produce.如果一个通道或一组通道关闭,但如果它们的生成速度很慢,它们可能还没有准备好进行读取。 Thus the current implementation cannot detect adequately the end of writes upon both channels.因此,当前的实现无法充分检测到两个通道上的写入结束。

https://go.dev/ref/spec#Select_statements https://go.dev/ref/spec#Select_statements

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.如果一个或多个通信可以进行,则通过统一的伪随机选择选择一个可以进行的通信。 Otherwise, if there is a default case, that case is chosen.否则,如果存在默认情况,则选择该情况。 If there is no default case, the "select" statement blocks until at least one of the communications can proceed.如果没有默认情况,“select”语句会阻塞,直到至少有一个通信可以继续。

Though, in your specific code, it does something the standard idioms dont provide an out of the box solution.虽然,在您的特定代码中,它做了一些标准习语不提供开箱即用的解决方案。 Indeed, multiple workers write over multiple channels in parallel.实际上,多个工作人员并行写入多个通道。 Thus an additional structure, a sync.WaitGroup, is required to appropriately synchronize the concurrent worker execution so that the channels are closed when all writes are done.因此,需要一个额外的结构 sync.WaitGroup 来适当地同步并发工作线程的执行,以便在所有写入完成时关闭通道。

As for the reads, the current implementation attempts to read from multiple channels, but does not implement a logic to exit the loop upon write completions.至于读取,当前的实现尝试从多个通道读取,但没有实现在写入完成时退出循环的逻辑。 You could use the receive syntax within a select to quit the loop when both ok values are false, or another sync.WaitGroup to read both channels within their own routines and block main until the waitgroup has completed.您可以使用 select 中的接收语法在两个 ok 值为 false 时退出循环,或者另一个 sync.WaitGroup 在自己的例程中读取两个通道并阻塞 main 直到等待组完成。

You can refer both other answers for implementation details for both previously described situations.您可以参考其他两个答案以了解前面描述的两种情况的实现细节。

Here is an alternative,这是一个替代方案,

https://go.dev/play/p/YLMB6g95n_p https://go.dev/play/p/YLMB6g95n_p


package main

import (
    "fmt"
    "sync"
)

type waitGroup struct{ sync.WaitGroup }

func (wg *waitGroup) Add(fn func()) {
    wg.WaitGroup.Add(1)
    go func() {
        fn()
        wg.WaitGroup.Done()
    }()
}
func (wg *waitGroup) Then(fn func()) {
    go func() {
        wg.WaitGroup.Wait()
        fn()
    }()
}

func main() {
    ch_1 := make(chan int)
    ch_2 := make(chan int)

    var produce waitGroup
    produce.Add(func() {
        worker_1(ch_1, ch_2)
    })
    produce.Add(func() {
        worker_2(ch_1, ch_2)
    })
    produce.Then(func() {
        close(ch_1)
        close(ch_2)
    })

    var consume waitGroup
    consume.Add(func() {
        for n := range ch_1 {
            fmt.Println("from ch1", n)
        }
    })
    consume.Add(func() {
        for n := range ch_2 {
            fmt.Println("from ch2", n)
        }
    })
    consume.Wait()

}

func worker_1(ch1, ch2 chan int) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

func worker_2(ch1, ch2 chan int) {
    for i := 101; i < 200; i++ {
        if i%2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM