解决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语言中json数据的读取和写出操作
Apr 28 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 Golang
Golang全局变量加锁的问题解决
May 08 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Go timer如何调度
Jun 09 Golang
Golang 语言控制并发 Goroutine的方法
Jun 30 Golang
修改并编译golang源码的操作步骤
Jul 25 Golang
golang内置函数len的小技巧
Jul 25 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
Golang ort 中的sortInts 方法
Apr 24 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计算整个mysql数据库大小的方法
2015/06/19 PHP
PHP几个实用自定义函数小结
2016/01/25 PHP
php接口技术实例详解
2016/12/07 PHP
php通过header发送自定义数据方法
2018/01/18 PHP
PHP实现文字写入图片功能
2019/02/18 PHP
ThinkPHP5框架中使用JWT的方法示例
2020/06/03 PHP
javascript 模拟JQuery的Ready方法实现并出现的问题
2009/12/06 Javascript
通过判断JavaScript的版本实现执行不同的代码
2010/05/11 Javascript
javascript学习之闭包分析
2010/12/02 Javascript
关于js获取radio和select的属性并控制的代码
2011/05/12 Javascript
jquery获取div宽度的实现思路与代码
2013/01/13 Javascript
javascript中不提供sleep功能如何实现这个功能
2014/05/27 Javascript
JavaScript判断表单中多选框checkbox选中个数的方法
2015/08/17 Javascript
JavaScript 2048 游戏实例代码(简单易懂)
2016/03/25 Javascript
模仿password输入框的实现代码
2016/06/07 Javascript
React Native基础入门之初步使用Flexbox布局
2018/07/02 Javascript
vue后台管理之动态加载路由的方法
2018/08/13 Javascript
微信小程序利用button控制条件标签的变量问题
2020/03/15 Javascript
理解python正则表达式
2016/01/15 Python
Python入门_浅谈for循环、while循环
2017/05/16 Python
在python里从协程返回一个值的示例
2019/02/19 Python
Pytorch实现LSTM和GRU示例
2020/01/14 Python
解决echarts中饼图标签重叠的问题
2020/05/16 Python
python文件操作seek()偏移量,读取指正到指定位置操作
2020/07/05 Python
Python使用xlrd实现读取合并单元格
2020/07/09 Python
pycharm专业版远程登录服务器的详细教程
2020/09/15 Python
10个顶级Python实用库推荐
2021/03/04 Python
以设计师精品品质提供快速时尚:Mostata
2019/05/10 全球购物
学生会招新策划书
2014/02/14 职场文书
连带责任保证书
2014/04/29 职场文书
阳光体育活动实施方案
2014/05/25 职场文书
先进事迹演讲稿
2014/09/01 职场文书
领导干部作风建设总结
2014/10/23 职场文书
2016优秀班主任个人先进事迹材料
2016/02/26 职场文书
详细了解java监听器和过滤器
2021/07/09 Java/Android
设置IIS Express并发数
2022/07/07 Servers