解决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结构体嵌套的切片数组操作
Apr 28 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
golang elasticsearch Client的使用详解
May 05 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
K8s部署发布Golang应用程序的实现方法
Jul 16 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Golang数据类型和相互转换
Apr 12 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
Python测试框架pytest核心库pluggy详解
Aug 05 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转换IP地址到真实地址的方法详解
2013/06/09 PHP
smarty模板引擎之配置文件数据和保留数据
2015/03/30 PHP
javaScript parseInt字符转化为数字函数使用小结
2009/11/05 Javascript
js怎么终止程序return不行换jfslk
2013/05/30 Javascript
input禁止键盘及中文输入,但可以点击
2014/02/13 Javascript
javascript的BOM
2016/05/03 Javascript
原生javascript移动端滑动banner效果
2017/03/10 Javascript
使用node.js搭建服务器
2017/05/20 Javascript
浅谈JavaScript中的属性:如何遍历属性
2017/09/14 Javascript
vue跨域解决方法
2017/10/15 Javascript
Angular 封装并发布组件的方法示例
2018/04/19 Javascript
JS使用栈判断给定字符串是否是回文算法示例
2019/03/04 Javascript
JavaScript实现无限级递归树的示例代码
2019/03/29 Javascript
vue实现滑动切换效果(仅在手机模式下可用)
2020/06/29 Javascript
如何正确理解vue中的key详解
2019/11/02 Javascript
[02:51]DOTA2 2015国际邀请赛中国区预选赛第一日战报
2015/05/27 DOTA
python装饰器使用方法实例
2013/11/21 Python
编写Python脚本批量下载DesktopNexus壁纸的教程
2015/05/06 Python
Python更新数据库脚本两种方法及对比介绍
2017/07/27 Python
用pandas中的DataFrame时选取行或列的方法
2018/07/11 Python
Python版名片管理系统
2018/11/30 Python
python实现将视频按帧读取到自定义目录
2019/12/10 Python
如何完美的建立一个python项目
2020/10/09 Python
CSS3新属性transition-property transform box-shadow实例学习
2013/06/06 HTML / CSS
css3 利用transform打造走动的2D时钟
2020/10/20 HTML / CSS
印尼极简主义和实惠的在线家具店:Fabelio
2019/03/27 全球购物
英国和世界各地预订便宜的酒店:LateRooms.com
2019/05/05 全球购物
水务局局长岗位职责
2013/11/28 职场文书
给校长的一封建议书
2014/03/12 职场文书
领导班子三严三实心得体会
2014/10/13 职场文书
股权转让协议书
2014/12/07 职场文书
2017新年晚会开幕词
2016/03/03 职场文书
导游词之泉州崇武古城
2019/12/20 职场文书
利用python Pandas实现批量拆分Excel与合并Excel
2021/05/23 Python
我家女友可不止可爱呢 公开OP主题曲无字幕动画MV
2022/04/11 日漫
Win10 最新稳定版本 21H2开始推送
2022/04/19 数码科技