
Go doesn’t provide a built-in “kill” mechanism for goroutines; instead, you must design cooperative termination using channels—typically by sending a signal (e.g., via a done or quit channel) and having the goroutine exit its loop gracefully.
go doesn’t provide a built-in “kill” mechanism for goroutines; instead, you must design cooperative termination using channels—typically by sending a signal (e.g., via a `done` or `quit` channel) and having the goroutine exit its loop gracefully.
To stop a goroutine safely and correctly, avoid busy-waiting (default cases), prevent reading from closed channels, and ensure the goroutine exits the loop upon receiving a shutdown signal.
✅ Correct Pattern: Use a done Channel for Graceful Exit
Here’s a clean, idiomatic approach:
done := make(chan struct{})
go func() {
for {
select {
case <h3>❌ Why Your Original Code Fails</h3>
-
close(run)is dangerous: After closingrun,always succeeds immediately (returning the zero value of <code>bool), causingmyFunc(c)to be called repeatedly—even afterquitis signaled. -
defaultcauses busy-looping: Thedefaultbranch makes the loop spin continuously, consuming CPU and potentially starving other goroutines. -
No synchronization: Sending to
quitorrunhappens after the goroutine starts—but there's no guarantee the goroutine has entered theselectbefore yourif x == trueexecutes. You risk missing the signal entirely.
✅ Better Design: Control Start/Stop via a Single Signal Channel
If you need to toggle execution (start/stop myFunc dynamically), use a sync.WaitGroup + atomic state or a dedicated control channel:
control := make(chan bool) // true = start, false = stop
done := make(chan struct{})
var active int32 // atomic flag
go func() {
defer close(done)
for {
select {
case start := <h3>? Key Takeaways</h3>
- ✅ Always
return(orbreakout of the loop) after receiving a shutdown signal — never just close another channel. - ✅ Never use
defaultin long-runningselectloops unless intentional and bounded (e.g., withtime.Sleep). - ✅ Prefer
chan struct{}overchan boolfor signaling — it’s zero-size, semantically clearer, and avoids accidental value reads. - ✅ Use
close(ch)only when all senders are done; for signaling, prefer sending a value or closing a dedicateddonechannel.
By embracing Go’s concurrency model—cooperative, channel-driven, and non-preemptive—you write safer, more maintainable, and debuggable concurrent code.










