Sending to a closed channel immediately panics your program, terminating the goroutine and potentially crashing the entire application. This is a hard runtime error in Go, distinct from receiving from a closed channel, which safely returns the zero value and a false boolean.
To prevent this, always ensure the sender checks if the channel is closed before writing, or rely on a single, well-defined closer pattern where only one goroutine is responsible for closing the channel. If you must handle dynamic scenarios, use a select statement with a default case or a separate "done" channel to coordinate shutdown, rather than blindly sending.
Here is a minimal example demonstrating the panic:
package main
import "fmt"
func main() {
ch := make(chan int)
close(ch) // Channel is closed immediately
// This line will panic: send on closed channel
ch <- 42
fmt.Println("This will never print")
}
If you run this, the output will be a panic message like panic: send on closed channel followed by a stack trace.
A safer pattern involves using a select statement with a default case to attempt a non-blocking send, or ensuring the receiver signals completion before the sender tries to write. However, the most robust approach is architectural: define a clear ownership model where the channel creator or a specific manager goroutine is the only entity allowed to call close().
If you need to gracefully handle a scenario where a channel might be closed unexpectedly by another part of the system, you can wrap the send in a recover block, though this is often a sign of a design flaw. A better alternative is to use a sync.Once to ensure the channel is closed only once, or to use a context to signal cancellation instead of closing the channel for control flow.
package main
import (
"fmt"
"sync"
)
func safeSend(ch chan int, val int, closed *bool, mu *sync.Mutex) {
mu.Lock()
defer mu.Unlock()
if *closed {
fmt.Println("Channel closed, skipping send")
return
}
ch <- val
}
func main() {
ch := make(chan int)
closed := false
var mu sync.Mutex
go func() {
close(ch)
mu.Lock()
closed = true
mu.Unlock()
}()
// This will skip the send instead of panicking
safeSend(ch, 10, &closed, &mu)
}
In production code, prefer using context.Context to signal cancellation and stop goroutines, rather than relying on closing channels as a primary control mechanism. This avoids the race condition where a sender tries to write after a receiver has closed the channel.