Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang select statement with channels and waitgroup

Tags:

go

Experimenting with Golang, I have created a function with a select statement that listens to two channels.

My problem is that the code seems to behave non-deterministically - sometimes it panics and sometimes it completes successfully.

My expectation is that this code should always panic. It should receive the error first, because it should be dispatched before the waitGroup has finished and therefore before the success channel is pushed to.

package main

import (
    "errors"
    "fmt"
    "sync"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)

    doSomething(success, errs)

    select {
    case err := <-errs:
        fmt.Println("error", err)
        panic(err)
    case <-success:
        fmt.Println("success")
    }
    fmt.Println("finished successfully")
}

func doSomething(success chan bool, errs chan error) {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        err := errors.New("Some error")
        errs <- err
    }()

    wg.Wait()
    success <- true
}
like image 665
willwoo Avatar asked Jan 25 '23 03:01

willwoo


1 Answers

Both channels are ready before the select statement; so it will chose via a uniform pseudo-random selection:

Let's replace the doSomething function call in your code, and put defer at the end of the function:

package main

import (
    "errors"
    "fmt"
    "sync"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        err := errors.New("some error")
        errs <- err
        wg.Done()
    }()

    wg.Wait()
    success <- true

    select {
    case err := <-errs:
        fmt.Println("error", err)
        panic(err)
    case <-success:
        fmt.Println("success")
    }
    fmt.Println("finished successfully")
}

As you see in the above code sample, the main goroutine waits at the wg.Wait() for the wg.Done() at this point in time the code is (almost) functionally equal to the following code, and both channels are ready before the select statement here:

package main

import (
    "errors"
    "fmt"
)

func main() {
    errs := make(chan error, 1)
    success := make(chan bool, 1)
    errs <- errors.New("some error")
    success <- true

    select {
    case err := <-errs:
        fmt.Println(err)
    case <-success:
        fmt.Println("success")
    }
}

Run:

$ go run .
some error

$ go run .
success

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.

like image 193
wasmup Avatar answered Feb 17 '23 20:02

wasmup