简体   繁体   中英

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.

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-

The language provides syntax to read a channel until closure

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.

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.

The runtime may enter the default branch of a select anytime none of the other cases are ready to read or write. 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

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.

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.

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.

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


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
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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