Reputation: 1105
I am trying to understand synchronisation in Goroutines. I have a code here that writes numbers from 0 to 4 on a channel and once done I read from the channel using range
and print the values.
wg.Wait()
and closing the channel in a separate Goroutine.package main
import (
"fmt"
"strconv"
"sync"
)
func putvalue(i chan string, value string, wg *sync.WaitGroup) {
i <- value
defer wg.Done()
}
func main() {
queue := make(chan string)
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go putvalue(queue, strconv.Itoa(i), &wg)
}
go func() {
wg.Wait()
close(queue)
}()
for elem := range queue {
fmt.Println(elem)
}
}
https://play.golang.org/p/OtaRP3Mm4lk
package main
import (
"fmt"
"strconv"
"sync"
)
func putvalue(i chan string, value string, wg *sync.WaitGroup) {
i <- value
defer wg.Done()
}
func main() {
queue := make(chan string)
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go putvalue(queue, strconv.Itoa(i), &wg)
}
wg.Wait()
close(queue)
for elem := range queue {
fmt.Println(elem)
}
}
https://play.golang.org/p/JXmdsdPKQPu
From what I can understand that in the second case the main thread execution stops and waits, but how is it different from doing it in a separate goroutine? Please help me understand this.
Upvotes: 4
Views: 4845
Reputation: 488213
It may help to think of each goroutine as a separate person (or gopher: https://blog.golang.org/gopher). When you go f()
you get a new person/gopher and give them the job of running the function. So, you have 5 extra gophers that are running this:
func putvalue(i chan string, value string, wg *sync.WaitGroup) {
i <- value
defer wg.Done()
}
Each one of the 5 runs up to the point where they reach the i <- value
line, and then they stop, waiting for a gopher to run up to the "get" side of the channel / mailbox and stick his hands through to get a string, which is the kind of package that goes into the i
channel / mailbox.
(Aside: the defer wg.Done()
should be the first line of the function, not the last. Or, just do wg.Done()
as the last line of the function.)
Now, if at this point you acquire a sixth extra gopher and make him do:
{
wg.Wait()
close(queue)
}
he'll stop inside wg.Wait()
, waiting.
Your main gopher now continues on to the for
loop. This reads from the mailbox, i.e., now your main gopher sticks his hands into the mailbox/window, on the "get" side of the channel. One of the five waiting gophers can finally put his string into your gopher's hands. Your main Gopher takes the string and brings it back to your for
loop. The one of the five gophers that were blocked can now execute his wg.Done()
and expire (presumably off to a happy land of retired gophers 😀).
Your main gopher continues in the for
loop, getting more packages through the mailbox. As he does so, the four gophers that are waiting on the mailbox "put" finish up and call wg.Done()
, which counts the workgroup counter down. When the count reaches zero, there are no gophers waiting to put packages into the mailbox any more, but now the gopher that was asleep, waiting for wg.Wait()
, is awakened. So he'll soon wake up and call close
.
If he hasn't called close
yet, your main gopher is stuck waiting for the next package. So only the one remaining gopher can do anything: he'll do the close. Or, maybe he woke up quickly and already did the close, but if so, your main gopher has already seen that the mailbox window is closed forever. Either way, your main gopher will already have seen, or will be about to see, that the mailbox window—the channel—is closed, and the for
loop will stop and your main gopher will return from main
and also head off to happy-retirement-land.
As Burak Serdar noted, though, without a separate gopher doing the wg.Wait()
followed by close
, it's your main gopher that is doing the wg.Wait()
. So he never gets around to the for
loop to read from the (still open) mailbox / channel. Your five gophers are asleep, waiting for a gopher to put his hands through the mailbox to get their packages. Your main gopher is asleep, waiting for the counter in the sync.WaitGroup
to go down to zero. Everyone's asleep!
Upvotes: 8
Reputation: 51587
In the first program, the main goroutine creates several goroutines where each goroutine start waiting on channel write. Then the main goroutine creates another goroutine that waits on wg.Wait()
. The main goroutine continues, reads from the channels, which also enables all goroutines one by one, and then they terminate, releasing the goroutine waiting on wg.Wait()
.
In the second program, you again create goroutines that wait on channel write, but this time, main goroutine calls wg.Wait()
. At this point, all goroutines you created are waiting for channel to become writable, and the main goroutine is waiting for the goroutines to end, which means deadlock
Upvotes: 1