大家好,我是煎鱼。

协程(goroutine)作为 Go 语言的扛把子,经常在各种 Go 工程项目中频繁露面,甚至有人会为了用 goroutine 而强行用他。

在 Go 工程师的面试中,也绕不开他,会有人问 ”如何停止一个 goroutine?”,一下子就把话题范围扩大了,这是一个涉及多个知识点的话题,能进一步深入问。

为此,今天煎鱼就带大家了解一下停止 goroutine 的方法!

goroutine 案例

在日常的工作中,我们常会有这样的 Go 代码,go 关键字一把搜起一个 goroutine:

func main() { ch := make(chan string, 6)go func() {for {ch <- "脑子进煎鱼了"}}()
}

初入 goroutine 大门的开发者可能就完事了,但跑一段时间后,他就可能会遇到一些问题,苦苦排查...

像是:当 goroutine 内的任务,运行的太久,又或是卡死了...就会一直阻塞在系统中,变成 goroutine 泄露,或是间接造成资源暴涨,会带来许多的问题。

如何在停止 goroutine,就成了一门必修技能了,不懂就没法用好 goroutine。

关闭 channel

第一种方法,就是借助 channel 的 close 机制来完成对 goroutine 的精确控制。

代码如下:

func main() {ch := make(chan string, 6)go func() {for {v, ok := <-chif !ok {fmt.Println("结束")return}fmt.Println(v)}}()ch <- "煎鱼还没进锅里..."ch <- "煎鱼进脑子里了!"close(ch)time.Sleep(time.Second)
}

在 Go 语言的 channel 中,channel 接受数据有两种方法:

msg := <-ch
msg, ok := <-ch

这两种方式对应着不同的 runtime 方法,我们可以利用其第二个参数进行判别,当关闭 channel 时,就根据其返回结果跳出。

另外我们也可以利用 for range 的特性:

go func() {for {for v := range ch {fmt.Println(v)}}}()

其会一直循环遍历通道 ch,直到其关闭为止,是颇为常见的一种用法。

定期轮询 channel

第二种方法,是更为精细的方法,其结合了第一种方法和类似信号量的处理方式。

代码如下:

func main() {ch := make(chan string, 6)done := make(chan struct{})go func() {for {select {case ch <- "脑子进煎鱼了":case <-done:close(ch)return}time.Sleep(100 * time.Millisecond)}}()go func() {time.Sleep(3 * time.Second)done <- struct{}{}}()for i := range ch {fmt.Println("接收到的值: ", i)}fmt.Println("结束")
}

在上述代码中,我们声明了变量 done,其类型为 channel,用于作为信号量处理 goroutine 的关闭。

而 goroutine 的关闭是不知道什么时候发生的,因此在 Go 语言中会利用 for-loop 结合 select 关键字进行监听,再进行完毕相关的业务处理后,再调用 close 方法正式关闭 channel。

若程序逻辑比较简单结构化,也可以不调用 close 方法,因为 goroutine 会自然结束,也就不需要手动关闭了。

使用 context

第三种方法,可以借助 Go 语言的上下文(context)来做 goroutine 的控制和关闭。

代码如下:

func main() {ch := make(chan struct{})ctx, cancel := context.WithCancel(context.Background())go func(ctx context.Context) {for {select {case <-ctx.Done():ch <- struct{}{}returndefault:fmt.Println("煎鱼还没到锅里...")}time.Sleep(500 * time.Millisecond)}}(ctx)go func() {time.Sleep(3 * time.Second)cancel()}()<-chfmt.Println("结束")
}

在 context 中,我们可以借助 ctx.Done 获取一个只读的 channel,类型为结构体。可用于识别当前 channel 是否已经被关闭,其原因可能是到期,也可能是被取消了。

因此 context 对于跨 goroutine 控制有自己的灵活之处,可以调用 context.WithTimeout 来根据时间控制,也可以自己主动地调用 cancel 方法来手动关闭。

干掉另外一个 goroutine

在了解了停止 goroutine 的 3 种经典方法后,又有小伙伴提出了新的想法。就是 “我想在 goroutineA 里去停止 goroutineB,有办法吗?

答案是不能,因为在 Go 语言中,goroutine 只能自己主动退出,一般通过 channel 来控制,不能被外界的其他 goroutine 关闭或干掉,也没有 goroutine 句柄的显式概念。

go/issues/32610

在 Go issues 中也有人提过类似问题,Dave Cheney 给出了一些思考:

  • 如果一个 goroutine 被强行停止了,它所拥有的资源会发生什么?堆栈被解开了吗?defer 是否被执行?

    • 如果执行 defer,该 goroutine 可能可以继续无限期地生存下去。

    • 如果不执行 defer,该 goroutine 原本的应用程序系统设计逻辑将会被破坏,这肯定不合理。

  • 如果允许强制停止 goroutine,是要释放所有东西,还是直接把它从调度器中踢出去,你想通过此解决什么问题?

这都是值得深思的,另外一旦放开这种限制。作为程序员,你维护代码。很有可能就不知道 goroutine 的句柄被传到了哪里,又是在何时何地被人莫名其妙关闭,非常糟糕...

总结

在今天这篇文章中,我们介绍了在 Go 语言中停止 goroutine 的三大经典方法(channel、context,channel+context)和其背后的使用原理。

同时针对 goroutine 不可以跨 goroutine 强制停止的原因进行了分析。其实 goroutine 的设计就是这样的,包括像 goroutine+panic+recover 的设计也是遵循这个原理,因此也有的 Go 开发者总是会误以为跨 goroutine 能有 recover 接住...

记住,在 Go 语言中每一个 goroutine 都需要自己承担自己的任何责任,这是基本原则。

(你已经是个成熟的 goroutine 了...)

关注煎鱼,吸取他的知识 

回答我,停止 Goroutine 有几种方法?相关推荐

  1. Spring Boot 优雅停止服务的几种方法

    作者 | 黄青石 来源 | https://www.cnblogs.com/huangqingshi/p/11370291.html 最近突然想到了优雅停止 SpringBoot 服务问题,在使用 S ...

  2. controller调用controller的方法_SpringBoot 优雅停止服务的几种方法

    转自:博客园,作者:黄青石 www.cnblogs.com/huangqingshi/p/11370291.html 在使用 SpringBoot 的时候,都要涉及到服务的停止和启动,当我们停止服务的 ...

  3. springboot 优雅关闭_Springboot 优雅停止服务的几种方法

    在使用Springboot的时候,都要涉及到服务的停止和启动,当我们停止服务的时候,很多时候大家都是kill -9 直接把程序进程杀掉,这样程序不会执行优雅的关闭.而且一些没有执行完的程序就会直接退出 ...

  4. 停止计算机sql服务,SQL Server启动和停止服务的三种方法

    一.为什么要启动SQL Server服务? 1.如果你不开启服务,去连接数据会出现报错信息 2.因为不连接到服务器,就对数据库操作不了 二.启动SQL Server的三种方法 第一种:后台启动服务 * ...

  5. SpringBoot 优雅停止服务的几种方法

    方法一 Springboot提供的actuator的功能,它可以执行shutdown, health, info <dependency><groupId>org.spring ...

  6. springboot 优雅停机_SpringBoot 优雅停止服务的几种方法 第309篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列?) 国内最全的Spring Boot系列之三 一分钟get:缓存穿透.缓存击穿.缓存雪崩 - 第304篇 布隆过滤器Bloom Filter竟然 ...

  7. autojs之停止脚本的6种方法

    停止所有正在运行的脚本 engines.stopAll(); 停止所有正在运行的脚本并显示停止的脚本数量 engines.stopAllAndToast(); 停止自己 engines.myEngin ...

  8. SpringBoot 优雅停止服务的几种方法 - 第309篇

    相关历史文章(阅读本文之前,您可能需要先看下之前的系列

  9. oracle停止一切进程,oracle启动/停止的几种方法以及 启动和停止过程中出错的解决办法...

    一.启动几种方法: 1. sqlplus /nolog connect /as sysdba startup 2. sqlplus /nolog connect /as sysdba startup ...

最新文章

  1. 基数排序算法LSD实现
  2. mongodb 运行状况,索引构建分析
  3. All input tensors must be on the same device
  4. Java在开发中应注意的问题_Java设计编程应该注意的几个问题
  5. kotlin学习笔记——类、函数、接口
  6. php数据访问(查询)
  7. 喜欢用Block的值得注意-Block的Retain Cycle的解决方法
  8. 纵横公路造价软件学习_通辽分公司组织开展2020年 养护工程造价预算培训
  9. java学习(99):车站卖票问题
  10. diabetes影响因子2017_Journal of Diabetes
  11. hive 操作(二)——使用 mysql 作为 hive 的metastore
  12. Android 接收短信
  13. Javascript:js借助jQuery和fileSave将表格存储到world
  14. 怎么做平面设计海报——黎乙丙
  15. 学编程必看:10道逻辑思维测试题(附答案)
  16. 华硕笔记本触控板设置 Smart Gesture
  17. 字符间距和文字效果(转)
  18. 机器学习之K均值(K-Means)算法
  19. easypoi模板单文件导出多个sheet页(单文件单sheet复制到多个导出)
  20. navicat导出数据库数据

热门文章

  1. 步步为营 .NET三层架构解析 四、Model设计(四种设计方式)
  2. ng-options track by 思考
  3. 英特尔、联发科、展讯等开始支持开源的物联网轻量化操作系统AliOS Lite
  4. Scala在挖财的应用实践
  5. 给thinkphp加个分页样式
  6. CodeForces - 351E Jeff and Permutation(贪心)
  7. 中石油训练赛 - Molecules(高斯消元解方程)
  8. SPOJ - COT Count on a tree(LCA+主席树+离散化)
  9. POJ - 2528 Mayor's posters(线段数+离散化)
  10. windows 2008 域 删除不活动计算机账号,如何删除域内非活动计算机账号?