解决go在函数退出后子协程的退出问题


Posted in Golang onApril 30, 2021

该问题来源于自己在读fabric源码时,看到的一个测试代码,在一个函数中启用协程,然后该函数退出了,由于平常没有这样处理过,以及受原有c++函数域的影响,认为函数退出,子协程应该也退出了呀。

这其实是自己对go协程的理解不到位引起的,go的协程作用域不是在某个函数中的,当然,如果那个函数是main函数,就符合要求了。

该代码为solo算法的测试代码:

func goWithWait(target func()) *waitableGo {
	wg := &waitableGo{
		done: make(chan struct{}),
	}
	go func() {
		target()//该协程会阻塞在这
		close(wg.done)//用来对外通知
	}()
	//外边结束,里边还不结束吗?
	return wg
}
// This test checks that if consenter is halted before a timer fires, nothing is actually written.
func TestHaltBeforeTimeout(t *testing.T) {
	batchTimeout, _ := time.ParseDuration("1ms")
	//support的构造还不清楚
	support := &mockmultichannel.ConsenterSupport{
		Blocks:          make(chan *cb.Block),
		BlockCutterVal:  mockblockcutter.NewReceiver(),
		SharedConfigVal: &mockconfig.Orderer{BatchTimeoutVal: batchTimeout},
	}
	defer close(support.BlockCutterVal.Block)
	bs := newChain(support)
	//bs.main是solo算法的启动函数,是个死循环,处理函数
	wg := goWithWait(bs.main)
	defer bs.Halt()//中止
	syncQueueMessage(testMessage, bs, support.BlockCutterVal)
	bs.Halt()
	select {
	case <-support.Blocks:
		t.Fatalf("Expected no invocations of Append")
	case <-wg.done:
	}
}

遇到该问题后,我写了几个测试:

单纯的函数退出,是不会影响协程的

package main
import "fmt"
var ch chan int
func test() int {
 ch = make(chan int)
 go func() {
  for {
   fmt.Println(<-ch)
   fmt.Println("hello")
  }
  fmt.Println("aaaa")
 }()
 //不阻塞,那go func()不会异常退出吗?
 //协程并不是函数,不会因为这个函数的退出而退出
 //test()启动一个deadloop子协程,这个会在主协程main结束后被强制退出
 return 0
}
func main() {
 c := test()
 ch <- 10
 fmt.Println("c", c)
}

我经常在main里边直接写协程的测试demo,main退出会结束主协程,之后会强制结束子协程,一般不会遇到上述在普通函数退出的问题,也没仔细思考,所以分析源码时有点困惑。

子协程启动子协程,父协程的退出,并没有影响到子协程

liudeMacBook-Pro:~ liu$ cat tmp.go 
package main
import (
	"fmt"
	"time"
)
func test() {
	go func() { //父协程
		defer func() {
			fmt.Println("exit dad")
		}()
		go func() { //子协程
			defer func() {
				fmt.Println("exit kid")
			}()
		}()
	}()
}
func main() {
	test()
	time.Sleep(time.Second)
}
liudeMacBook-Pro:~ liu$ go run tmp.go 
exit dad
exit kid

补充:golang中父子协程生命周期问题,以及通过context优雅关闭子协程

背景

上次基于mysql实现分布式锁,今天经过测试发现问题,主要是协程不断获取锁的逻辑存在问题,因为获取锁的协程挂掉之后,但其新生成的用来不断更新锁的协程并不会退出,导致锁一直不能被释放,究其原因如下

原因

通过下面代码即可说明

fmt.Println("main 函数 开始...")
	go func() {
		fmt.Println("父 协程 开始...")
		go func() {
			for {
				fmt.Println("子 协程 执行中...")
				timer := time.NewTimer(time.Second * 2)
				<-timer.C
			}
		}()
		time.Sleep(time.Second*5)
		fmt.Println("父 协程 退出...")
	}()
	time.Sleep(time.Second*10)
	fmt.Println("main 函数 退出")

main 函数 开始...

父 协程 开始...

子 协程 执行中...

子 协程 执行中...

子 协程 执行中...

父 协程 退出...

子 协程 执行中...

子 协程 执行中...

main 函数 退出

由此可以看出:

main 函数退出,所有协程退出

协程无父子关系,即在父协程开启新的协程,若父协程退出,不影响子协程

解决方式

通过context上下文来解决,当然也可以通过channel管道来解决,context解决方式如下:

fmt.Println("main 函数 开始...")
	go func() {
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		fmt.Println("父 协程 开始...")
		go func(ctx context.Context) {
			for {
				for {
					select {
					case <-ctx.Done():
						fmt.Println("子 协程 接受停止信号...")
						return
					default:
						fmt.Println("子 协程 执行中...")
						timer := time.NewTimer(time.Second * 2)
						<-timer.C
					}
				}
			}
		}(ctx)
		time.Sleep(time.Second*5)
		fmt.Println("父 协程 退出...")
	}()
	time.Sleep(time.Second*10)
	fmt.Println("main 函数 退出")

main 函数 开始...

父 协程 开始...

子 协程 执行中...

子 协程 执行中...

子 协程 执行中...

父 协程 退出...

子 协程 接受停止信号...

main 函数 退出

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
go原生库的中bytes.Buffer用法
Apr 25 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
基于Go Int转string几种方式性能测试
Apr 28 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
go类型转换及与C的类型转换方式
May 05 Golang
go select编译期的优化处理逻辑使用场景分析
Jun 28 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
Go语言实现Base64、Base58编码与解码
Jul 26 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
如何解决goland,idea全局搜索快捷键失效问题
Apr 03 Golang
Go gorilla/sessions库安装使用
Aug 14 Golang
Go语言 go程释放操作(退出/销毁)
golang DNS服务器的简单实现操作
golang slice元素去重操作
Apr 30 #Golang
Golang中interface{}转为数组的操作
Apr 30 #Golang
解决Go gorm踩过的坑
Apr 30 #Golang
Golang 如何实现函数的任意类型传参
Apr 29 #Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 #Golang
You might like
PHP 中的面向对象编程:通向大型 PHP 工程的办法
2006/12/03 PHP
php数组函数序列之array_flip() 将数组键名与值对调
2011/11/07 PHP
通过php快速统计某个数据库中每张表的数据量
2012/09/04 PHP
PHP循环遍历数组的3种方法list()、each()和while总结
2014/11/19 PHP
微信公众平台开发关注及取消关注事件的方法
2014/12/23 PHP
基础的WordPress插件制作教程
2015/11/24 PHP
php中bind_param()函数用法分析
2017/03/28 PHP
PHP实现二叉树深度优先遍历(前序、中序、后序)和广度优先遍历(层次)实例详解
2018/04/20 PHP
PHP实现计算器小功能
2020/08/28 PHP
lyhucSelect基于Jquery的Select数据联动插件
2011/03/29 Javascript
jQuery使用$.ajax进行即时验证实例详解
2015/12/11 Javascript
jQuery中on方法使用注意事项详解
2017/02/15 Javascript
vue-cli项目中怎么使用mock数据
2017/09/27 Javascript
浅谈Vue.use的使用
2018/08/29 Javascript
Vue项目数据动态过滤实践及实现思路
2018/09/11 Javascript
angular 服务随记小结
2019/05/06 Javascript
Moment.js实现多个同时倒计时
2019/08/26 Javascript
javascript设计模式 ? 代理模式原理与用法实例分析
2020/04/16 Javascript
vue移动端的左右滑动事件详解
2020/06/17 Javascript
Angular进行简单单元测试的实现方法实例
2020/08/16 Javascript
windows如何把已安装的nodejs高版本降级为低版本(图文教程)
2020/12/14 NodeJs
python 生成不重复的随机数的代码
2011/05/15 Python
python中使用smtplib和email模块发送邮件实例
2014/04/22 Python
详解Python 解压缩文件
2019/04/09 Python
python3 小数位的四舍五入(用两种方法解决round 遇5不进)
2019/04/11 Python
PyTorch基本数据类型(一)
2019/05/22 Python
python用TensorFlow做图像识别的实现
2020/04/21 Python
HTML5 Canvas的常用线条属性值总结
2016/03/17 HTML / CSS
管理失职检讨书
2014/02/12 职场文书
行政部工作岗位职责范本
2014/03/05 职场文书
行风评议整改报告
2014/11/06 职场文书
2015年服务员个人工作总结
2015/05/27 职场文书
CocosCreator入门教程之网络通信
2021/04/16 Javascript
Python编程编写完善的命令行工具
2021/09/15 Python
Apache Hudi集成Spark SQL操作hide表
2022/03/31 Servers
Redis全局ID生成器的实现
2022/06/05 Redis