Background: Channels are one of the fundamental concepts of the CSP model in Go. They are designed for data exchange between goroutines. Working with channels requires special care to avoid deadlocks, leaks, and panics when closing.
Issue: Closing and further using channels often leads to panic (panic: send on closed channel). Working with nil channels or attempting to read from a closed channel without checks can yield unexpected results. Different goroutines can simultaneously write to and read from the same channel, requiring clearly defined lifecycle logic.
Solution: A channel should always be closed only from the side where no one will write to the channel anymore (usually the producer). After closing, values can still be read from the channel until it is exhausted, but writing will result in panic. It is convenient to check for closure using the second return value from the channel:
Code example:
ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i } close(ch) }() for v := range ch { fmt.Println(v) }
Key features:
1. Is it necessary to close the channel if it is read by only one goroutine and no one else expects additional values?
Answer: It is not necessary to close the channel if no one is waiting for its closure through range. However, if a for loop is used as for v := range ch — yes, the channel must be closed, otherwise the loop will not terminate.
2. What happens if you read from a closed channel?
Answer: Values will be returned until the buffer is exhausted, then zero value and false will be returned. It is safe to read until the end after closing.
ch := make(chan int, 1) ch <- 42 close(ch) v, ok := <-ch // v = 42, ok = true v, ok = <-ch // v = 0, ok = false
3. Who should close the channel: the reader or the writer?
Answer: Always the "writer" (the one who sends values and knows when work is done). The "reader" should not close the channel to avoid race conditions.
In an application, several goroutines write to one channel, and one reads. Someone closed the channel in the reader while another goroutine was still trying to send — panic.
Pros: Conditionally simple interaction logic. Cons: Unpredictability of the closing moment, panics, race conditions.
The writer keeps track of all goroutines, uses sync.WaitGroup, and closes the channel only after all writers are done. The reader correctly concludes the range loop after the channel is closed, safely handles errors.
Pros: Correct synchronization, no leaks or deadlocks, predictable behavior. Cons: Slightly more complex logic, requires explicit completion of all tasks.