使用context实现通知退出机制
- 原理:
context
包提供了一种跨goroutine
传递截止时间、取消信号和其他请求范围值的方法。context.Context
接口有两个关键方法Done()
和Cancel()
,Done()
返回一个通道,当context
被取消或超时时,该通道会被关闭,goroutine
可以监听这个通道来得知需要退出。
- 示例代码:
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("收到退出信号,开始退出")
return
default:
fmt.Println("工作中...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go worker(ctx)
time.Sleep(5 * time.Second)
}
使用channel实现通知退出机制
- 原理:创建一个类型为
struct{}
的通道(空结构体占用内存极小),当需要通知goroutine
退出时,向该通道发送数据,goroutine
通过监听这个通道来得知需要退出。
- 示例代码:
package main
import (
"fmt"
"time"
)
func worker(quit chan struct{}) {
for {
select {
case <-quit:
fmt.Println("收到退出信号,开始退出")
return
default:
fmt.Println("工作中...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
quit := make(chan struct{})
go worker(quit)
time.Sleep(3 * time.Second)
close(quit)
time.Sleep(2 * time.Second)
}
并发场景下对性能的影响
- 资源释放:
- 使用context:
context
机制在取消时会自动通知所有关联的goroutine
,有利于资源的及时释放。例如,当一个goroutine
负责连接数据库,在收到context
取消信号后可以关闭数据库连接。但是,如果在goroutine
内没有正确处理context
取消信号,可能导致资源无法及时释放,如文件未关闭、网络连接未断开等。
- 使用channel:通过
channel
通知退出时,goroutine
同样需要在收到信号后手动释放资源。如果忘记关闭相关资源,也会造成资源泄漏。但channel
相对更灵活,开发人员可以更好地控制资源释放的时机和逻辑。
- 数据竞争:
- 使用context:
context
本身是线程安全的,它通过内部机制确保在多个goroutine
之间安全传递取消信号。然而,如果在goroutine
内部,在处理context
取消信号的同时访问共享资源,而没有适当的同步机制(如mutex
),仍然可能发生数据竞争。
- 使用channel:在使用
channel
实现退出机制时,如果多个goroutine
同时向退出channel
发送信号(虽然通常不应该这样做,但在复杂逻辑中可能出现),或者在发送/接收信号与访问共享资源之间没有同步,也可能导致数据竞争。但只要合理设计,确保在操作共享资源时使用适当的同步原语,就可以避免数据竞争问题。