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:
- One goroutine is ready to receive from
exit
. All other goroutines busy somewhere else. - The value sent by main is received by the ready goroutine.
- That goroutine sends a value to
exit
that is received bymain()
.
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.