0

The problem is that both the goOne and goTwo functions are sending values to the channels ch1 and ch2 respectively, but there is no corresponding receiver for these values in the main function. This means that the channels are blocked and the program is unable to proceed. As a result, the select statement in the main function is unable to read from the channels, so it always executes the default case.

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    ch1 := make(chan string)
    ch2 := make(chan string)

    wg.Add(2)
    go goOne(&wg, ch1)
    go goTwo(&wg, ch2)

    select {
    case <-ch1:
        fmt.Println(<-ch1)
        close(ch1)

    case <-ch2:
        fmt.Println(<-ch2)
        close(ch2)

    default:
        fmt.Println("Default Case")
    }
    wg.Wait()

}

func goTwo(wg *sync.WaitGroup, ch2 chan string) {
    ch2 <- "Channel 2"
    wg.Done()
}

func goOne(wg *sync.WaitGroup, ch1 chan string) {
    ch1 <- "Channel 1"
    wg.Done()
}

Output:

Default Case
fatal error: all goroutines are asleep - deadlock!

goroutine 1 \[semacquire\]:
sync.runtime_Semacquire(0xc000108270?)
/usr/local/go/src/runtime/sema.go:62 +0x25
sync.(\*WaitGroup).Wait(0x4b9778?)
/usr/local/go/src/sync/waitgroup.go:139 +0x52
main.main()
/home/nidhey/Documents/Go_Learning/goroutines/select.go:29 +0x2af

goroutine 6 \[chan send\]:
main.goOne(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:39 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:14 +0xc5

goroutine 7 \[chan send\]:
main.goTwo(0x0?, 0x0?)
/home/nidhey/Documents/Go_Learning/goroutines/select.go:33 +0x28
created by main.main
/home/nidhey/Documents/Go_Learning/goroutines/select.go:15 +0x119\```

I'm looking for a different pattern such as select to handle the case when the channels are blocked.

To fix the issue, I've added a <-ch1 or <-ch2 in the main function after wg.Wait() to receive the values sent to the channels and unblock them

N.F.
  • 3,844
  • 3
  • 22
  • 53
  • You need to provide more information on what you want to achieve with your code, and what you expect to happen. It is unclear if you want to wait for both goroutines to finish, or just one of them. – Emin Laletovic Jan 25 '23 at 08:40
  • Imagine if we are having two api end points, API1 & API2 which are returning same data but are hosted on different regions. So what I want to do, I need to make API calls for both apis in two different function ie goroutines and as soon as any one api sends us response back, I want to process the data received. So for that Im check whcih api is fetching data first using select block. – Nidhey Indurkar Jan 25 '23 at 08:44
  • 1
    It's not guaranteed to execute the default, it just does here because neither channel is ready to receive at that moment. Remove the default to see what happens (which of course is another deadlock because you're trying to receive two values from a channel which will only ever have a single value sent). If you say you are just looking for the first value received from the channel, then assign that value to a variable and don't try to receive again. You are discarding that first value. – JimB Jan 25 '23 at 14:48
  • I updated the answer with your clarifications – thrownew Feb 01 '23 at 07:52

3 Answers3

1

It's not entirely clear what you want to do. If you want to wait for both goroutines to complete their work and get the result of their work into the channel, you don't need a weight group (because it won't be reached).

You can do something like this.

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    for {
        select {
        case v := <-ch1:
            fmt.Println("Done ch1:", v)
            ch1 = nil
        case v := <-ch2:
            fmt.Println("Done ch2:", v)
            ch2 = nil
        case <-time.After(time.Second):
            fmt.Println("I didn't get result so lets skip it!")
            ch1, ch2 = nil, nil
        }
        if ch1 == nil && ch2 == nil {
            break
        }
    }
}

func goTwo(ch chan string) {
    ch <- "Channel 2"
}

func goOne(_ chan string) {
    //ch1 <- "Channel 1"
}

UPD:

Imagine if we are having two api end points, API1 & API2 which are returning same data but are hosted on different regions. So what I want to do, I need to make API calls for both apis in two different function ie goroutines and as soon as any one api sends us response back, I want to process the data received. So for that Im check whcih api is fetching data first using select block.

package main

import (
    "context"
    "fmt"
    "math/rand"
    "time"
)

func main() {

    regions := []string{
        "Europe",
        "America",
        "Asia",
    }

    // Just for different results for each run
    rand.Seed(time.Now().UnixNano())
    rand.Shuffle(len(regions), func(i, j int) { regions[i], regions[j] = regions[j], regions[i] })

    output := make(chan string)

    ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
    for i, region := range regions {
        go func(ctx context.Context, region string, output chan <- string, i int) {

            // Do call with context
            // If context will be cancelled just ignore it here

            timeout := time.Duration(i)*time.Second
            fmt.Printf("Call %s (with timeout %s)\n", region, timeout)
            time.Sleep(timeout) // Simulate request timeout

            select {
            case <-ctx.Done():
                fmt.Println("Cancel by context:", region)
            case output <- fmt.Sprintf("Answer from `%s`", region):
                fmt.Println("Done:", region)
            }
        }(ctx, region, output, i)
    }

    select {
    case v := <-output:
        cancel() // Cancel all requests in progress (if possible)
        // Do not close output chan to avoid panics: When the channel is no longer used, it will be garbage collected.
        fmt.Println("Result:", v)
    case <-ctx.Done():
        fmt.Println("Timeout by context done")
    }

    fmt.Println("There is we already have result or timeout, but wait a little bit to check all is okay")
    time.Sleep(5*time.Second)
}
thrownew
  • 119
  • 4
0

Firstly you have a race condition in that your channel publishing goroutines will probably not have been started by the time you enter the select statement, and it will immediately fall through to the default.

But assuming you resolve this (e.g. with another form of semaphore) you're on to the next issue - your select statement will either get chan1 or chan2 message, then wait at the bottom of the method, but since it is no longer in the select statement, one of your messages won't have arrived and you'll be waiting forever.

You'll need either a loop (twice in this case) or a receiver for both channels running in their own goroutines to pick up both messages and complete the waitgroup.

But in any case - as others have queried - what are you trying to achieve?

user1016765
  • 2,935
  • 2
  • 32
  • 48
0

You can try something like iterating over a single channel (assuming both channels return the same type of data) and then count in the main method how many tasks are completed. Then close the channel once all the tasks are done. Example:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string)

    go goOne(ch)
    go goTwo(ch)
    doneCount := 0

    for v := range ch {
        fmt.Println(v)
        doneCount++
        if doneCount == 2 {
            close(ch)
        }
    }
}

func goTwo(ch chan string) {
    ch <- "Channel 2"
}

func goOne(ch chan string) {
    ch <- "Channel 1"
}