解决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 相关文章推荐
golang如何去除多余空白字符(含制表符)
Apr 25 Golang
goland 清除所有的默认设置操作
Apr 28 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
浅谈golang package中init方法的多处定义及运行顺序问题
May 06 Golang
golang 实现时间戳和时间的转化
May 07 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
golang语言指针操作
Apr 14 Golang
Golang 字符串的常见操作
Apr 19 Golang
Go调用Rust方法及外部函数接口前置
Jun 14 Golang
Go gRPC进阶教程gRPC转换HTTP
Jun 16 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中的string类型使用说明
2010/07/27 PHP
php实现在限定区域里自动调整字体大小的类实例
2015/04/02 PHP
php实现对两个数组进行减法操作的方法
2015/04/17 PHP
Laravel ORM 数据model操作教程
2019/10/21 PHP
PHP 命名空间和自动加载原理与用法实例分析
2020/04/29 PHP
jQuery 版本的文本输入框检查器Input Check
2009/07/09 Javascript
JavaScript 空位补零实现代码
2010/02/26 Javascript
jQuery each()方法的使用方法
2010/03/18 Javascript
javascript 词法作用域和闭包分析说明
2010/08/12 Javascript
javascript 使用 NodeList需要注意的问题
2013/03/04 Javascript
js中Math之random,round,ceil,floor的用法总结
2013/12/26 Javascript
JS将所有对象s的属性复制给对象r(原生js+jquery)
2014/01/25 Javascript
JQuery 两种方法解决刚创建的元素遍历不到的问题
2016/04/13 Javascript
JSON与XML的区别对比及案例应用
2016/11/11 Javascript
EasyUI的DataGrid每行数据添加操作按钮的实现代码
2017/08/22 Javascript
vue vue-Router默认hash模式修改为history需要做的修改详解
2018/09/13 Javascript
使用vue引入maptalks地图及聚合效果的实现
2020/08/10 Javascript
基于NodeJS开发钉钉回调接口实现AES-CBC加解密
2020/08/20 NodeJs
VUE+Element实现增删改查的示例源码
2020/11/23 Vue.js
Python实现动态添加类的属性或成员函数的解决方法
2014/07/16 Python
PyCharm代码格式调整方法
2018/05/23 Python
自适应线性神经网络Adaline的python实现详解
2019/09/30 Python
Pandas的Apply函数具体使用
2020/07/21 Python
css3实现文字扫光渐变动画效果的示例
2017/11/07 HTML / CSS
HTML5中input[type='date']自定义样式与日历校验功能的实现代码
2017/07/11 HTML / CSS
使用postMessage让 iframe自适应高度的方法示例
2019/10/08 HTML / CSS
阳光体育:Sunny Sports(购买露营和远足设备)
2018/08/07 全球购物
门卫岗位安全职责
2013/12/13 职场文书
党支部公开承诺践诺书
2014/03/28 职场文书
公司承诺书怎么写
2014/05/24 职场文书
甘南现象心得体会
2014/09/11 职场文书
国庆节标语大全
2014/10/08 职场文书
公司感谢信范文
2015/01/22 职场文书
表扬信范文
2019/04/22 职场文书
2019年自助餐厅创业计划书模板
2019/08/22 职场文书
SQLServer2008提示评估期已过解决方案
2021/04/12 SQL Server