Moon soon
Moon soon

Reputation: 2856

WaitGroup goroutines with channel

I am learning WaitGroup from the blog https://nathanleclaire.com/blog/2014/02/15/how-to-wait-for-all-goroutines-to-finish-executing-before-continuing/

the code:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    messages := make(chan int)
    var wg sync.WaitGroup

    // you can also add these one at 
    // a time if you need to 

    wg.Add(3)
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 3)
        messages <- 1
    }()
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 2)
        messages <- 2
    }() 
    go func() {
        defer wg.Done()
        time.Sleep(time.Second * 1)
        messages <- 3
    }()
    go func() {
        for i := range messages {
            fmt.Println(i)
        }
    }()

    wg.Wait()
}

I think it should print 3, 2 and 1 in order. But it only prints 3, 2 but 1 is missing, what's the problem?

You can tree it on https://play.golang.org/p/kZCvDhykYM

Upvotes: 3

Views: 5068

Answers (4)

Abhishek D K
Abhishek D K

Reputation: 2427

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    messages := make(chan int)
    var wg sync.WaitGroup
    wg.Add(3)

    // created this goroutine to wait for other
    // goroutines to complete and to close the channel
    go func() {
     wg.Wait()
     close(messages)
    }()

    go func() {
      defer wg.Done()
      time.Sleep(time.Second * 3)
      messages <- 1
    }()

    go func() {
      defer wg.Done()
      time.Sleep(time.Second * 2)
      messages <- 2
    }()

    go func() {
      defer wg.Done()
      time.Sleep(time.Second * 1)
      messages <- 3
    }()

    // this for loop is blocked in main goroutine.
    // till it reads all messages and channel is closed.
    for v := range messages {
      fmt.Print(v)
    }
}

Upvotes: 2

0x7ff
0x7ff

Reputation: 11

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    messages := make(chan int)
    var wg sync.WaitGroup

    // you can also add these one at
    // a time if you need to

    wg.Add(3)
    go func() {
        defer wg.Done()

        time.Sleep(time.Second * 3)
        messages <- 1
    }()
    go func() {
        defer wg.Done()

        time.Sleep(time.Second * 2)
        messages <- 2
    }()
    go func() {
        defer wg.Done()

        time.Sleep(time.Second * 1)
        messages <- 3
    }()

exit:
    for {
        select {
        case i, ok := <-messages:
            if !ok {
                break exit
            }
            fmt.Println(i)
        default:
            time.Sleep(time.Second)
        }
    }

    wg.Wait()
}

Upvotes: 0

pgras
pgras

Reputation: 12770

The mentioned blog starts with following comment:

EDIT: As pointed out by effenn in this Reddit comment, a lot of information in this article is “dangerously inaccurate”. OOPS! I’ve written a followup/correction article here for your viewing pleasure, but I’m leaving this article up for “historical purposes”.

The Reddit comment and the followup article both describe the problem and give a solution to your problem. (Adding time.Sleep(...) to make the program work the way you expect is really hacky...)

Upvotes: 0

zerkms
zerkms

Reputation: 254906

Right after the latest messages <- 1, the deferred wg.Done() is invoked which releases wg.Wait() in the end of the program and the program quits. When a program quits all the goroutines get killed, so the printing goroutine does not have a chance to print the latest value.

If you put something like time.Sleep(time.Second * 1) right after wg.Done() you would be able to see all the output lines.

Upvotes: 5

Related Questions