- 使用context.Context
- 实现方式:
在Go 1.7及以上版本中,
context.Context
提供了一种优雅地取消goroutine
的方式。通过context.WithCancel
、context.WithTimeout
等函数创建context
对象,并传递给goroutine
。在goroutine
内部,通过监听context.Done()
通道来判断是否需要停止。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine stopped")
return
default:
fmt.Println("working...")
time.Sleep(500 * time.Millisecond)
}
}
}(ctx)
time.Sleep(3 * time.Second)
}
- 适用场景:适用于需要在一定时间内取消操作,或者当外部条件满足时取消操作的场景。例如HTTP请求处理中的
goroutine
,当请求超时或者客户端断开连接时,需要取消相关的goroutine
。
- 注意事项:创建
context
时要合理设置超时时间。如果context
没有正确传递到所有需要取消的goroutine
中,可能导致部分goroutine
无法被正确取消。同时,context
主要用于控制goroutine
的生命周期,不应该用于传递其他业务数据。
- 使用共享的停止信号变量
- 实现方式:
定义一个共享的布尔变量,
goroutine
内部通过定期检查这个变量来判断是否需要停止。
package main
import (
"fmt"
"time"
)
func main() {
stop := false
go func() {
for {
if stop {
fmt.Println("goroutine stopped")
return
}
fmt.Println("working...")
time.Sleep(500 * time.Millisecond)
}
}()
time.Sleep(2 * time.Second)
stop = true
time.Sleep(1 * time.Second)
}
- 适用场景:适用于简单的场景,
goroutine
内部的逻辑较为简单,并且不需要处理复杂的超时或外部取消信号的情况。例如一些独立运行的后台任务,当应用程序关闭时需要停止这些任务。
- 注意事项:如果在多个
goroutine
中共享这个变量,需要注意并发安全问题,通常可以使用sync.Mutex
来保护对该变量的读写操作。同时,goroutine
需要定期检查这个变量,不能有长时间阻塞的操作,否则可能无法及时响应停止信号。
- 通过通道通信
- 实现方式:
创建一个通道,在需要停止
goroutine
时向该通道发送一个信号,goroutine
通过监听该通道来停止运行。
package main
import (
"fmt"
"time"
)
func main() {
stopCh := make(chan struct{})
go func() {
for {
select {
case <-stopCh:
fmt.Println("goroutine stopped")
return
default:
fmt.Println("working...")
time.Sleep(500 * time.Millisecond)
}
}
}()
time.Sleep(2 * time.Second)
close(stopCh)
time.Sleep(1 * time.Second)
}
- 适用场景:适用于
goroutine
之间通过通道进行通信,并且需要通过通道来控制goroutine
停止的场景。比如在生产者 - 消费者模型中,当消费者不再需要数据时,可以通过通道通知生产者停止生产。
- 注意事项:要确保通道的正确关闭,避免
goroutine
因为通道未关闭而一直阻塞。同时,如果在多个goroutine
中监听同一个通道来停止,要注意处理好并发情况下的逻辑。