Reputation: 962
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
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
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
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