shellfly
shellfly

Reputation: 962

Why Go's channel can close twice?

When I am do some go practices code, I encounter a problem that a channel can be closed twice like this:

// jobs.go

package main

import (
   "fmt"
)

func main() {
    fmt.Println("Hello, playground")
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j,more := <-jobs

            fmt.Println("receive close: ", j, more)
            done <- true   
        }
    }()

    close(jobs)
    <- done
}

Output:

~ go run jobs.go
Hello, playground
receive close:  0 false
receive close:  0 false

But when I close the channel twice manually, I got panic: close of closed channel.

Why the code above can receive close twice?

Upvotes: 5

Views: 22795

Answers (3)

logsv
logsv

Reputation: 544

Go channel no getting closed twise. your are passing done <- true after first print

j,more := <-jobs

fmt.Println("receive close: ", j, more)
done <- true

so it printed two time

receive close:  0 false
receive close:  0 false

If you use done <- true before print then it will print one time only, will get closed.

done <- true
j,more := <-jobs

fmt.Println("receive close: ", j, more)

Output :

receive close:  0 false

Upvotes: 0

icza
icza

Reputation: 417572

A channel can only be closed once, attempting to close a closed channel panics.

But receiving from a closed channel is not limited, receiving from a closed channel:

A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.

A Go app runs until its main goroutine runs (given "normal" circumstances), or from another point of view: a Go app terminates when its main goroutine terminates, that is, the main() function returns. It does not wait for other non-main goroutines to complete.

You started a 2nd goroutine with an endless for loop, with no way of terminating. So that loop will keep going until the main() function –which runs in the concurrent, main goroutine– returns. Since the for loop first receives from jobs, it waits for the main goroutine to close it (this receive operation can only then proceed). Then the main goroutine wants to receive from done, so that waits until the 2nd goroutine sends a value on it. Then the main goroutine is "free" to terminate at any moment. Your 2nd goroutine running the loop may receive an additional value from jobs since it's closed, but the subsequent send on done will block as there are nobody receiving from it anymore (and it's unbuffered).

Receiving from a channel until it's closed is normally done using for range, which exits if the channel is closed:

for j := range jobs {
    fmt.Println("received: ", j)
    done <- true
}

Of course this would cause a deadlock in your case, as the loop body would never be reached as nobody sends anything on jobs, and so the loop would never enter its body to send a value on done which is what the main goroutine waits for.

Upvotes: 12

abhink
abhink

Reputation: 9126

The jobs channel is not being closed twice. It's closed only once when close(jobs) is called. The output you are seeing is because of how the goroutine and main threads are executing.

When the goroutine fires, it does not immidieatly start running. Instead, the program control goes to this part:

  close(jobs) // <--
  <- done
}

and jobs is closed. Main thread then hangs on the next receive on done.

Now context switching occurs and the goroutine starts running. It reads from a closed jobs, prints the appropriate values (false for more indicating a closed channel), and sends a true along done.

However, the loop is able to execute once more and the goroutine blocks on the next send on done. Now main wakes up again, receives on done and terminates the program.

Upvotes: 1

Related Questions