写点什么

通知协程退出 (取消) 的几种方式

作者:fliter
  • 2024-01-30
    上海
  • 本文字数:1912 字

    阅读完需:约 6 分钟

在 Go 语言中,控制 goroutine 的退出或取消很重要,这能使资源得到合理利用,避免潜在的内存泄露。



如下是一些在 Go 中通知协程退出的常见方式:


  1. 使用通道(Channel:通过发送特定的信号或关闭通道来通知协程退出。这是最简单直接的方法。

  2. 使用 contextcontext 包提供了一种更标准化的方式来传递取消信号、超时、截止时间等控制信息。

  3. 使用 sync.WaitGroup:虽然 WaitGroup 本身不用于发送取消信号,但它可以用来等待一组协程完成,通常与其他方法(如通道)结合使用来控制协程的退出。


<br>

1. 使用通道

<br>


package main
import ( "fmt" "time")
func worker(exitChan chan bool) { for { select { case <-exitChan: fmt.Println("Worker exiting.") return default: fmt.Println("Working...") time.Sleep(1 * time.Second) } }}
func main() { exitChan := make(chan bool) go worker(exitChan)
time.Sleep(3 * time.Second) // 模拟做了一些工作 exitChan <- true // 通知协程退出 time.Sleep(1 * time.Second) // 给协程时间退出}
复制代码


输出:


Working...Working...Working...Working...Worker exiting.
复制代码


<br>


在线代码


<br>

2. 使用 context

<br>


package main
import ( "context" "fmt" "time")
func worker(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("Worker exiting.") return default: fmt.Println("Working...") time.Sleep(1 * time.Second) } }}
func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx)
time.Sleep(3 * time.Second) // 模拟做了一些工作 cancel() // 通知协程退出 time.Sleep(1 * time.Second) // 给协程时间退出}
复制代码


输出:


Working...Working...Working...Working...Worker exiting.
复制代码


<br>


在线代码


<br>


在上面这两个示例中,当主函数完成其工作后,通过通道发送信号或调用 cancel 函数来通知协程退出。使用 context 包是更推荐的做法,因为其提供了一种更标准化和灵活的方式来管理协程的生命周期。


<br>

3. 使用 sync.WaitGroup 控制协程退出

<br>


sync.WaitGroup 主要用于等待一组协程的完成。其不直接提供通知协程退出的机制,但可以与其他方法(如通道)结合使用来控制协程的退出。

示例:

package main
import ( "fmt" "sync" "time")
func worker(id int, wg *sync.WaitGroup, stopCh chan struct{}) { defer wg.Done() for { select { case <-stopCh: // 接收退出信号 fmt.Printf("Worker %d stopping\n", id) return default: fmt.Printf("Worker %d working\n", id) time.Sleep(time.Second) } }}
func main() { var wg sync.WaitGroup stopCh := make(chan struct{})
workerCount := 3 wg.Add(workerCount)
for i := 0; i < workerCount; i++ { go worker(i, &wg, stopCh) }
time.Sleep(3 * time.Second) // 模拟工作 close(stopCh) // 发送退出信号给所有协程
wg.Wait() // 等待所有协程退出 fmt.Println("All workers stopped")}
复制代码


输出:


Worker 0 workingWorker 2 workingWorker 1 workingWorker 2 workingWorker 0 workingWorker 1 workingWorker 0 workingWorker 2 workingWorker 1 workingWorker 2 stoppingWorker 0 stoppingWorker 1 stoppingAll workers stopped
复制代码


<br>


在线代码


<br>


在上例中,stopCh 通道用于通知协程退出。当关闭 stopCh 时,所有监听这个通道的协程都会接收到信号,并优雅地停止执行。


但我觉得这个例子并不好,本质上成了和**1.使用通道(Channel)**一样了..


sync.WaitGroup的真正作用是卡在wg.Wait()处,直到wg.Done()被执行(wg.Add()增加的值被减为 0),才会继续往下执行. 比如往往用于防止 goroutine 还没执行完,主协程就退出了


<br>


<br>


另外,如果是性能敏感场景,往往使用原子操作(Atomic)在多个协程之间安全地共享状态(原子操作用于安全地读写共享状态,可以用来设置一个标志,协程可以定期检查这个标志来决定是否退出),而不使用通道来做协程间的通信


<br>


更多参考:


golang context 父子任务同步取消信号


Go 程序员面试笔试宝典-context 如何被取消


如何退出协程 goroutine (其他场景)


go协程取消

用户头像

fliter

关注

www.dashen.tech 2018-06-21 加入

Software Engineer. Focus on Micro Service,Containerization

评论

发布
暂无评论
通知协程退出(取消)的几种方式_fliter_InfoQ写作社区