6

It's one of example code of Golang. But cannot understand why 'done' channel need in this case.

https://gobyexample.com/closing-channels

There is no reason to sent true to done channel. We can know jobs channel is done when "sent all jobs" message is printed, isn't it?

I deleted code that relative to done channel and result is still same.

https://play.golang.org/p/xOmzobFpTQ

J.Done
  • 2,783
  • 9
  • 31
  • 58

4 Answers4

8

No the result is not the same:
Your main goroutine exits before your received job goroutine in many situations (e.g. different CPU loads, and it is nondeterministic and system dependent behavior), so in that way you cannot guarantee that all jobs received, e.g. just Add

time.Sleep(500)

before

fmt.Println("received job", j)

to see this, try it on The Go Playground:

// _Closing_ a channel indicates that no more values
// will be sent on it. This can be useful to communicate
// completion to the channel's receivers.

package main

import (
    "fmt"
    "time"
)

// In this example we'll use a `jobs` channel to
// communicate work to be done from the `main()` goroutine
// to a worker goroutine. When we have no more jobs for
// the worker we'll `close` the `jobs` channel.
func main() {
    jobs := make(chan int, 5)
    //done := make(chan bool)

    // Here's the worker goroutine. It repeatedly receives
    // from `jobs` with `j, more := <-jobs`. In this
    // special 2-value form of receive, the `more` value
    // will be `false` if `jobs` has been `close`d and all
    // values in the channel have already been received.
    // We use this to notify on `done` when we've worked
    // all our jobs.
    go func() {
        for {
            j, more := <-jobs
            if more {
                time.Sleep(500)
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                //done <- true
                return
            }
        }
    }()

    // This sends 3 jobs to the worker over the `jobs`
    // channel, then closes it.
    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)
    fmt.Println("sent all jobs")

    // We await the worker using the
    // [synchronization](channel-synchronization) approach
    // we saw earlier.
    //<-done
}

output:

sent job 1
sent job 2
sent job 3
sent all jobs

instead of:

sent job 1
received job 1
received job 2
sent job 2
sent job 3
received job 3
received all jobs
sent all jobs

See:
Goroutine does not execute if time.Sleep included
Why is time.sleep required to run certain goroutines?
Weird channel behavior in go

Community
  • 1
  • 1
5

TL;DR: There is a race condition---You got lucky.

If you didn't have the done channel, then the output of the program is non-deterministic.

Depending on the thread execution order, the main thread may exit before the goroutine finished its processing thereby causing the goroutine to be killed mid way.

By forcing the main thread to read from the done channel, we're forcing the main thread to wait until there is some data to be consumed in the done channel. This gives us a neat synchronization mechanism wherein the goroutine informs the main thread that it is done by writing to the done channel. This in turn causes the main thread's blocking <- done to finish and causes the program to terminate.

Guru Prasad
  • 4,053
  • 2
  • 25
  • 43
3

I think the accepted answer did not go into the exact reasons why.


The go language belongs to the procedural paradigm, meaning that every instruction is executed linearly. When a go routine is forked from the main go routine, it goes off on its own little adventure, leaving the main thread to return.

The buffered channel has a capacity of 5, which means it will not block until the buffer is full. It will also block if it is empty (a channel with zero capacity is inherently unbuffered).

Since there are only 4 iterations (0 to <=3), the read operation will not block.

By instructing the main thread to read from the done channel, we're forcing the main thread to wait until there is some data to be consumed in the done channel. When the iteration is over, the else branch is executed and the write operation done <- true causes the release of the <- done read operation in the main thread. The read operation waits to pull the now inserted value off of done.

After reading from done, the main Go routine is no longer blocked, hence terminating successfully.

Remario
  • 3,813
  • 2
  • 18
  • 25
1
  • Sent doesn't mean the job is done, when the job takes a long time to finish.

  • The jobs channel is buffered, so even if the job is sent, the job may not even be received by the worker at the time.

Lucas
  • 9,871
  • 5
  • 42
  • 52
Sam Liao
  • 43,637
  • 15
  • 53
  • 61