In this article, php editor Xiaoxin will introduce an important issue about Go programs: the situation of exiting before the goroutine work is completed. In the Go language, goroutine is a lightweight thread that can execute tasks concurrently. However, when our program may exit before the goroutine work is completed, we need to understand how to handle this situation to ensure that our program completes the task correctly. In the following content, we will explore this problem and provide some solutions to solve it.
I'm having trouble understanding how to properly block and close channels. I'm starting an arbitrary number of workers and I'm finding that my main function either exits before the workers complete or hangs due to unclosed channels. I need a better way to stop the worker from reading the channel without exiting the main channel, and then gracefully close the channel when finished to end the loop. Any attempts I make end in deadlock.
I tried a few things including using a wait group, but the problem persists. I noticed that by adding time.sleep
the program works as expected, but commenting it out results in no work being done.
time.sleep(time.duration(10 * time.second))
This is a runnable example https://go.dev/play/p/qhqnj-ajqbi which preserves sleep
. This is the broken code with the sleep timeout commented out.
package main import ( "fmt" "sync" "time" ) // some complicated work func do(num int, ch chan<- int) { time.sleep(time.duration(500 * time.millisecond)) ch <- num } func main() { results := make(chan int) // for some number of required complicated work for i := 0; i < 53; i++ { go do(i, results) } var wg sync.waitgroup // start 3 workers which can process results for i := 0; i < 3; i++ { wg.add(1) go func(id int) { defer wg.done() worker(id, results) }(i) } // handle closing the channel when all workers complete go func() { wg.wait() close(results) }() //time.sleep(time.duration(10 * time.second)) fmt.println("donezo") } // process the results of do() in a meaningful way func worker(id int, ch <-chan int) { fmt.println("starting worker", id) for i := range ch { fmt.println("channel val:", i) } }
I also tried moving defer wg.done()
inside the worker()
func but it's the same problem and doesn't work without sleep.
// process the results of do() in a meaningful way func worker(wg *sync.WaitGroup, id int, ch <-chan int) { fmt.Println("starting worker", id) defer wg.Done() for i := range ch { fmt.Println("channel val:", i) } }
Did I choose the wrong paradigm, or am I just using the wrong paradigm?
I originally asked "Can I make some small adjustments to my code to make it work? Or do I have to rethink this problem?" I The answer found is that, yes, there is a small adjustment.
I had to learn an interesting basic concept about channels: you can read data from a closed channel, i.e. drain the channel. As mentioned in my original example range
never terminates because I can't find a good place to close the channel, and even when I force it in other creative ways the program behaves poorly Behavior
This is due to a subtle difference in the "real" code where the time required to process the channel contents is longer than the time required to populate the channel and Things are out of sync.
Since there is no clear practical way to close the channel in my sender (which is recommended in 99% of channel tutorials), when you have multiple workers reading the channel and the workers don't know about it, by It's actually acceptable to do this with a goroutine in main where the last value is read.
I wrapped the worker in its own sync.waitgroup
and used worker.wait()
to block the program exit, thus allowing the work to "finish". When there is no more data to send, I close()
the channel independently, i.e. I block by waiting for the writer to finish using their own wait group. close Provides a termination case for range loops because when the channel's default value is returned, i.e. the eof type is reached when the end of the channel is reached, it will end. A blocking rendezvous channel has no endpoint until it is closed.
My take on this is that if you don't know how many values will be pushed in parallel, go has no way of knowing the length of the unbuffered channel because it's in scope, until you close it. . Since it's closed, it means reading whatever is left until the termination value or the end. workers.wait()
will block until completed.
Examples of resolved operations https://www.php.cn/link/2bf0ccdbb4d3ebbcb990af74bd78c658
Example of reading closed channel https://www.php.cn/link/d5397f1497b5cdaad7253fdc92db610b
Output
filling 0 filling 1 filling 2 filling 3 filling 4 filling 5 filling 6 filling 7 filling 8 filling 9 closed empyting 0 empyting 1 empyting 2 empyting 3 empyting 4 empyting 5 empyting 6 empyting 7 empyting 8 empyting 9
The above is the detailed content of Go program exits before goroutine work completes. For more information, please follow other related articles on the PHP Chinese website!