Reputation: 2625
I have a snippet of code that I am trying to understand based on how I put the close
call and the location
func main() {
ch := make(chan int, 2)
go func(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
fmt.Println("Func goroutine sends data: ", i)
}
//Pos1 - Works perfectly
//close(ch)
}(ch)
fmt.Println("Main goroutine sleeps 2 seconds")
time.Sleep(time.Second * 2)
fmt.Println("Main goroutine begins receiving data")
//Pos2 - Puts in only 2 ints 1 and 2 and then prints only that
//close(ch)
for d := range ch {
fmt.Println("Main goroutine received data:", d)
}
//Pos3 - Throws fatal error
close(ch)
}
I have been trying to understand and read blogs on this but not able to understand somethings still
close
work at this position?for loop
not throwing an exception when it is trying to write more elements to the channel that is closed?fatal error: all goroutines are asleep - deadlock!
though all 5 ints are printed. Why is that?Upvotes: 1
Views: 4713
Reputation: 363
Modifying the code snippets with some peppered print statements to add on to the excellent answer by @leaf bebop, to clarify what's happening.
Pos 2, but make main wait a bit
func main() {
ch := make(chan int, 2)
go func(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
fmt.Println("Func goroutine sends data: ", i)
}
fmt.Println("goR work is done")
}(ch)
fmt.Println("Main goroutine sleeps 2 seconds")
time.Sleep(time.Second * 2)
fmt.Println("Main goroutine begins receiving data")
close(ch)
for d := range ch {
fmt.Println("Main goroutine received data:", d)
}
fmt.Println("Main waiting, will the other goR panic?")
time.Sleep(time.Second * 10)
}
Main goroutine sleeps 2 seconds
Func goroutine sends data: 1
Func goroutine sends data: 2
Main goroutine begins receiving data
Main goroutine received data: 1
panic: send on closed channel
goroutine 6 [running]:
main.main.func1(0x0?)
/tmp/sandbox3746599466/prog.go:15 +0x3b
created by main.main
/tmp/sandbox3746599466/prog.go:13 +0x85
Program exited.
So, given enough time (main routine didn't exit), the scheduler went back to the go routine, where we wrote to a closed (closed by main) channel - Panic!
Pos 2, but only send 2 values, i.e not writing to a closed channel
func main() {
ch := make(chan int, 2)
go func(ch chan int) {
// Send only 2 values
for i := 1; i <= 2; i++ {
ch <- i
fmt.Println("Func goroutine sends data: ", i)
}
fmt.Println("goR work is done")
}(ch)
fmt.Println("Main goroutine sleeps 2 seconds")
time.Sleep(time.Second * 2)
fmt.Println("Main goroutine begins receiving data")
close(ch)
for d := range ch {
fmt.Println("Main goroutine received data:", d)
}
fmt.Println("Main waiting, will the other goR panic?")
time.Sleep(time.Second * 10)
fmt.Println("Main exiting")
}
Main goroutine sleeps 2 seconds
Func goroutine sends data: 1
Func goroutine sends data: 2
goR work is done
Main goroutine begins receiving data
Main goroutine received data: 1
Main goroutine received data: 2
Main waiting, will the other goR panic?
Main exiting
Program exited.
No panic, no spinning for ever. Main routine just exits.
Pos 3, check if main got unblocked?
func main() {
ch := make(chan int, 2)
go func(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
fmt.Println("Func goroutine sends data: ", i)
}
fmt.Println("Routine done with work")
}(ch)
fmt.Println("Main goroutine sleeps 2 seconds")
time.Sleep(time.Second * 2)
fmt.Println("Main goroutine begins receiving data")
for d := range ch {
fmt.Println("Main goroutine received data:", d)
}
fmt.Println("Have I closed this channel?")
close(ch)
fmt.Println("I have")
}
Main goroutine sleeps 2 seconds
Func goroutine sends data: 1
Func goroutine sends data: 2
Main goroutine begins receiving data
Main goroutine received data: 1
Main goroutine received data: 2
Main goroutine received data: 3
Func goroutine sends data: 3
Func goroutine sends data: 4
Func goroutine sends data: 5
Routine done with work
Main goroutine received data: 4
Main goroutine received data: 5
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox3301646152/prog.go:26 +0x194
Program exited.
Main never got even to close the channel, it was blocked in the range loop. It'll stay here forever, until it gets a value, or someone closes the channel. We don't have any other routine to do that, so deadlock.
Upvotes: 0
Reputation: 8232
But I thought to do a range over a buffered channel, the range function has to know beforehand how many elements to iterate over and for that channel must be closed.
That assumption is wrong and the root of all misunderstandings.
The behavior of ranging over a channel is described in the Go Spec: https://golang.org/ref/spec#For_statements
For channels, the iteration values produced are the successive values sent on the channel until the channel is closed. If the channel is nil, the range expression blocks forever.
The channel does not need to be closed when the for statement is evaluated and the statement does not need to know number of elements.
So, in your code, when you put close
in Pos1, it is indeed the right way to do it. When you put it in Pos3, the for loop waits the channel to be closed, which can only happen after the for loop itself, so it is a deadlock.
Putting close
in Pos2 is buggy and the behavior is a little tricky. It is possible to raise an error, yet it is possible to just output two numbers. This is because when the channel is closed before the for loop, the loop can run without block and then main()
returns. And when main()
returns, the Go program ends. Whether it raises an error solely depends on if the scheduler switch to goroutine between the process, which is not guaranteed.
Upvotes: 6