Home > database >  exiting multiple go routines waiting on an unbuffered channel
exiting multiple go routines waiting on an unbuffered channel

Time:10-09

I was trying to exit multiple Goroutines Simultaneously. According to https://www.godesignpatterns.com/2014/04/exiting-multiple-goroutines-simultaneously.html there is a well defined way to do it.

Another approach i see is the following

package main

import (
    "fmt"
    "time"
)

func main() {
    var inCh chan int = make(chan int, 100)
    var exit chan bool = make(chan bool)

    for i := 0; i < 20; i   {
        go func(instance int) {
            fmt.Println("In go routine ", instance)
            for {
                select {
                case <-exit:
                    fmt.Println("Exit received from ", instance)
                    exit <- true
                    return
                case value := <-inCh:
                    fmt.Println("Value=", value)
                }
            }
        }(i)
    }

    time.Sleep(1 * time.Second)

    exit <- true
    <-exit   // Final exit
    fmt.Println("Final exit")
}

But I am confused and i really don't get it why the unbuffered channel at the end is executed as a last statement. In reality I have 20 go routines listening for exit channel. Randomly one will receive it and send it to another. Why always the reception within go routines is taking place and only when all of them are finished the channel reception with comment "// Final Exit" will execute ?

I will really appreciate if someone can give me an explanation.

CodePudding user response:

Use close() for cancelation as shown in the linked article.

The code in question is not guaranteed to work. Here's a scenario where it fails:

  1. One goroutine is ready to receive from exit. All other goroutines busy somewhere else.
  2. The value sent by main is received by the ready goroutine.
  3. That goroutine sends a value to exit that is received by main().

The other goroutines do not exit because no more values are sent to exit. See this playground example that uses time.Seep to induce the problem scenario.

Why always the reception within go routines is taking place and only when all of them are finished the channel reception with comment "// Final Exit" will execute ?

The program executes as if the channel maintains an ordered queue of waiting goroutines, but there's nothing in the specification that guarantees that behavior. Even if the channel has an ordered queue, the program can encounter the scenario above if a goroutine is doing something other than waiting on receive from exit.

CodePudding user response:

If you notice the output of you program

In go routine  6
In go routine  0
In go routine  7
.
.
Exit received from  6
Exit received from  0
Exit received from  7
.
.
Final exit

They get called in the same( or almost the same) order of how they were started. If none of your Go routines are busy the first one registered will be used. This is just an implementation of the runtime and I would not count on this behavior.

Your final exit was the last channel to be listened on so it is used last.

If you remove the time.Sleep after your loop your final exit will get called almost immediately and most of your go routines will not receive the exit signal

Output with out time.Sleep (will very between runs)

In go routine  0
Exit received from  0
In go routine  1
In go routine  2
In go routine  3
In go routine  4
In go routine  5
In go routine  6
In go routine  7
In go routine  14
In go routine  15
In go routine  16
In go routine  17
In go routine  18
In go routine  19
Final exit

CodePudding user response:

Consider this slight modification.

package main

import (
    "fmt"
)

func main() {
    var exit chan int = make(chan int)
        var workers = 20
    for i := 0; i < workers; i   {
        go func(instance int) {
            fmt.Println("In go routine ", instance)
            for {
                select {
                case i := <-exit:
                    fmt.Println("Exit", i, "received from ", instance)
                    exit <- i-1
                    return
                }
            }
        }(i)
    }
    exit <- workers
    fmt.Println("Final exit:", <-exit)
}

Here, I've done 3 things: First, I removed the unused channel, for brevity. Second, I removed the sleep. third, I changed the exit channel to an int channel that is decremented by every pass. If I pass the number of workers in, any value other than 0 from the "Final" message indicates dropped workers.

Here's one example run:

% go run t.go
In go routine  8
In go routine  5
In go routine  0
In go routine  2
Exit 20 received from  8
Exit 19 received from  5
Final exit: 18
In go routine  13

When main calls time.Sleep it doesn't get scheduled until the sleep is over. The other goroutines all have this time to set up their channel readers. I can only assume, because I can't find it written anywhere, that channel readers are likely to be queued in roughly chronological error - thus, the sleep guarantees that main's reader is the last.

If this is consistent behavior, it certainly isn't reliable.

See Multiple goroutines listening on one channel for many more thoughts on this.

  •  Tags:  
  • go
  • Related