解决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语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
goland 恢复已更改文件的操作
Apr 28 Golang
Golang中interface{}转为数组的操作
Apr 30 Golang
Golang: 内建容器的用法
May 05 Golang
goland 设置project gopath的操作
May 06 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
入门学习Go的基本语法
Jul 07 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
Golang 结构体数据集合
Apr 22 Golang
Go gorilla securecookie库的安装使用详解
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
window+nginx+php环境配置 附配置搭配说明
2010/12/29 PHP
php压缩HTML函数轻松实现压缩html/js/Css及注意事项
2013/01/27 PHP
CodeIgniter错误mysql_connect(): No such file or directory解决方法
2014/09/06 PHP
新浪刚打开页面出来的全屏广告代码
2007/04/02 Javascript
兼容FF和IE的动态table示例自写
2013/10/21 Javascript
[原创]推荐10款最热门jQuery UI框架
2014/08/19 Javascript
使用控制台破解百小度一个月只准改一次名字
2015/08/13 Javascript
jquery 将当前时间转换成yyyymmdd格式的实现方法
2016/06/01 Javascript
js实现首屏延迟加载实现方法 js实现多屏单张图片延迟加载效果
2017/07/17 Javascript
浅谈vue引入css,less遇到的坑和解决方法
2018/01/20 Javascript
vue+jquery+lodash实现滑动时顶部悬浮固定效果
2018/04/28 jQuery
浅谈Vue 数据响应式原理
2018/05/07 Javascript
使用Vue实现图片上传的三种方式
2018/07/17 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
详解React 条件渲染
2020/07/08 Javascript
vue组件入门知识全梳理
2020/09/21 Javascript
Python实现读取目录所有文件的文件名并保存到txt文件代码
2014/11/22 Python
Python函数式编程指南(二):从函数开始
2015/06/24 Python
windows及linux环境下永久修改pip镜像源的方法
2016/11/28 Python
Python实现简单的文本相似度分析操作详解
2018/06/16 Python
基于Python实现扑克牌面试题
2019/12/11 Python
使用Python画了一棵圣诞树的实例代码
2020/11/27 Python
python实现简单的学生管理系统
2021/02/22 Python
CSS3之边框多颜色Border-color属性使用示例
2013/10/11 HTML / CSS
CSS3 @keyframes简单动画实现
2018/02/24 HTML / CSS
会计专业毕业生自我评价
2013/09/25 职场文书
妇产科护士自我鉴定
2013/10/15 职场文书
医学类导师推荐信范文
2013/11/19 职场文书
维修工先进事迹
2014/05/29 职场文书
平安家庭事迹材料
2014/12/20 职场文书
搞笑婚庆主持词
2015/06/29 职场文书
高中化学教学反思
2016/02/22 职场文书
pytorch Dropout过拟合的操作
2021/05/27 Python
使用redis实现延迟通知功能(Redis过期键通知)
2021/09/04 Redis
springboot读取resources下文件的方式详解
2022/06/21 Java/Android
win10拖拽文件时崩溃怎么解决?win10文件不能拖拽问题解决方法
2022/08/14 数码科技