你确定没有滥用 goroutine 吗

news2024/7/6 19:00:21

写在前面

学习 golang ,路还很长呢,犹记得刚开始学习 golang 的时候,写起来确实非常简单,有很多包和工具使用,不需要重复造轮子,但是要真的学好一门语言作为工具,对于其原理是非常有必要学懂的

并发错误

golang 天生高并发,编码的时候,就会出现滥用 goroutine 的情况,我们来看看都是如何滥用的

func main() {
	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println(" the num is ", i)
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

xdm 看看上面这个简单的程序运行 go run main.go 输出会是什么呢?

是会输出 0 到 9 吗?,我们来实际看看效果

# go run main.go
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 the num is  10
 program over !!

哦豁,这是为啥,明明循环了 10 次,应该每一次递增 1 的打印出结果才对呀

其实我们看到的这种现象属于 并发错误

解决错误

我们尝试着在 匿名函数中传入参数 i, 看看效果会不会好一点

func main() {
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println(" the num is ", i)
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

再次执行 go run main.go 查看输出

# go run main.go
 the num is  0
 the num is  1
 the num is  2
 the num is  3
 the num is  4
 the num is  5
 the num is  6
 the num is  7
 the num is  8
 the num is  9
 program over !!

果然,这才是我们想要的结果

那么回过头来细细看代码,我们可以发现,i 是主协程中的变量,主协程会修改 i 地址上的值, 变量 i 的地址一直在被重复使用,可是多个子协程也在不停的读取 i 的值,就导致了并发错误

避免这种并发错误,就可以用我们上面用到的传参拷贝即可

探究

我们再来确认一下,是不是 i 的地址一直是同一个

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf(" i = %d , &i = %p \n", i,&i)

		go func() {
			fmt.Printf(" the i  = %d , &i = %p \n", i,&i)
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

程序运行起来效果如下,主协程和子协程调用的 i 是同一个 i,地址完全相同

我们再来看看解决并发错误的时候,i 的地址又是有何不同

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf(" i = %d , &i = %p \n", i,&i)

		go func(i int) {
			fmt.Printf(" the i  = %d , &i = %p \n", i,&i)
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

我们可以看出,主协程中的 i 地址仍然是一样的,这个没错,但是子协程里面的 i 每一个协程的 i 变量地址都不一样,每个协程输出的都是属于自己的变量 i ,因此不会有上述的错误

程序崩溃 panic

有时候我们编码,会开辟多个协程,但是没有处理好协程中可能会 panic 的情况,若子协程挂掉,那么主协程也会随之挂掉,这里我们需要特别注意

举一个简单的例子

func main() {
	for i := 0; i < 5; i++ {

		go func() {
			a := 10
			b := 0
			fmt.Printf(" the i  = %d \n", a/b)
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

上面这个程序很明显,就是为了造 panic 的,是一个除 0 的异常,可想而知,整个程序会因为子协程的 panic 而挂掉

运行程序后报错信息如下:

# go run main.go
panic: runtime error: integer divide by zero

goroutine 5 [running]:
main.main.func1()
        /home/admin/golang_study/later_learning/goroutine_test/main.go:24 +0x11
created by main.main
        /home/admin/golang_study/later_learning/goroutine_test/main.go:21 +0x42
exit status 2

加入处理手段

我们在每一个子协程退出前都会去处理是否会有 panic,那么子协程的 panic 就不会导致 主协程挂掉了,这里谨记

func main() {
	for i := 0; i < 5; i++ {

		go func() {
			defer func() {
				if err := recover(); err != nil {
					fmt.Println("recover one goroutine panic")
				}
			}()
			a := 10
			b := 0
			fmt.Printf(" the i  = %d \n", a/b)
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

程序运行效果如下:

# go run main.go
recover one goroutine panic
recover one goroutine panic
recover one goroutine panic
recover one goroutine panic
recover one goroutine panic
 program over !!

很明显程序是没有 panic 的,因为每一个子协程发生的 panic 都被处理掉了,我们还可以使用 golang 提供的 runtime 包来将 具体的 panic 信息打印出来,便于分析问题

来写一个简单的例子

func main() {
	for i := 0; i < 5; i++ {

		go func() {
			defer func() {
				if err := recover(); err != nil {
					buf := make([]byte, 512)
					last := runtime.Stack(buf, false)
					fmt.Println("last == ",last)
					buf = buf[:last]
					fmt.Printf("[PANIC]%v\n%s\n", err, buf)
				}
			}()

			a := 10
			b := 0
			fmt.Printf(" the i  = %d \n", a/b)
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(" program over !! ")
}

此处我们运用了 runtime.Stack(buf, false) 来计算goroutine panic 的堆栈信息的字节数,并最终打印出来

我们先来看效果

我们将 panic 堆栈信息的字节数打印出来,并且将 panic 的具体信息也打印出来, 最重要的是程序没有崩溃

通过使用上述的方法就可以让子协程的 panic 不影响主协程的同时还可以打印出子协程 panic 的堆栈信息

可以看看源码

可以看看源码对于该函数的解释就明白了

Stack 将调用 goroutine 的堆栈跟踪格式化为 buf,并返回写入buf的字节数。

如果为 true, Stack 将格式化所有其他 goroutine 的堆栈跟踪在当前 goroutine 的跟踪之后进入 buf。

golang 的技巧还很多,咱们需要用起来才能够体现它的价值

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

极限多标签学习综述(Extreme Multi-label Learning)

A Survey on Extreme Multi-label Learning 先给地址&#xff1a; https://arxiv.org/abs/2210.03968 博主曾整理过Multi-Label Image Classification&#xff08;多标签图像分类&#xff09;&#xff0c;但这类任务中所用的数据集往往较小&#xff0c;分类数量并不多。但在更…

JavaScript的原型链

JavaScript的原型链 JavaScript的继承主要是通过原型链实现的&#xff0c;所以理解原型链是掌握JavaScript继承的关键一环。原型链的继承的基本思想是通过原型链继承多个引用类型的属性和方法。 理解原型链 关于原型链的定义与理解&#xff1a; 每个构造函数都有一个原型对…

Python实现将位图描摹为彩色矢量 svg 图片的源代码,Python实现位图转彩色矢量代码

Color Trace 这是一个将位图描摹为彩色矢量 svg 图片的程序&#xff0c;是一个命令行工具&#xff0c;使用 Python 脚本实现&#xff0c;运行环境 Python3.8。 ✨ 效果 以一个字帖图片为例&#xff0c;这是 png 格式的位图&#xff08;370KB&#xff09;&#xff1a; 这是颜…

多智能体强化学习环境【星际争霸II】SMAC环境配置

多智能体强化学习这个领域中&#xff0c;很多Paper都使用的一个环境是——星际争多智能体挑战(StarCraft Multi-Agent Challenge, SMAC)。最近也配置了这个环境&#xff0c;把中间一些步骤记录下来。2022.12.26 文章目录1 环境介绍1.1 相关论文1.2 项目代码地址2 安装过程3 相关…

2023年pmp的考试时间是什么时候?(含pmp资料)

不出意外&#xff0c;按照原计划&#xff0c;就是3、6、9、12月&#xff0c;22年11月延期考试地区的考生或者退考的估计会在3月或者6月考。具体就及时关注官网消息。 ​新版中文报名网站&#xff1a;中国国际人才交流基金会 这里说一下PMP的基本考试情况&#xff1a; 【考试注…

模型实战(2)之YOLOv5 实时实例分割+训练自己数据集

模型实战&#xff08;2&#xff09;之YOLOv5 实时实例分割训练自己数据集 本文将详解YOLOv5实例分割模型的使用及从头训练自己的数据集得到最优权重&#xff0c;可以直接替换数据集进行训练的训练模型可通过我的gitcode进行下载&#xff1a;https://gitcode.net/openmodel/yolo…

使用matplotlib画图 + python色彩大全

目录画线画点散点画点的形状、线的形状画点线在特定位置写文字plt.legend()中图例的位置方法一 plt.legend(loc4)方法二 plt.legend(bbox_to_anchor(num1, num2))方法三 bbox_to_anchor(1.05, 1), loc2, borderaxespad0保存图片指定图片大小网格线根据自己的需求做了一个画图的…

图的最短路径

文章目录单源最短路径-Dijkstra算法单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法单源最短路径-Dijkstra算法 针对一个带权有向图G&#xff0c;将所有结点分为两组S和Q&#xff0c;S是已经确定最短路径的结点集合&#xff0c;在初始时为空&#xff08;初始…

如何使用监控诊断工具Arthas(阿尔萨斯)

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

【python】实现精美圣诞树-拿下女神不是梦

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

Java Web基础面试题

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…

【K3s】第4篇 一篇文章带你了解使用Kompose

目录 1、Kompose介绍 2、安装Kompose 3、docker-compose文件转换为k8s文件 1、Kompose介绍 kompose是一个帮助熟悉 Kubernetes 的用户迁移到k8s的工具。 获取 Docker Compose 文件并将其转换为 Kubernetes 资源。 kompose是一个方便的工具&#xff0c;可以从本地 Docker …

Ffuf爆破神器(超详细)

目录为什么是Ffuf基本使用最基本的使用多个字典同时使用带cookie扫描&#xff08;-b&#xff09;静默模式&#xff08;-s&#xff09;递归扫描&#xff08;-recursion&#xff09;指定扩展名&#xff08;-e&#xff09;POST请求爆破方式1&#xff1a;指明请求地址和请求体【不推…

iOS 自动化测试踩坑(一): 技术方案、环境配置与落地实践

移动端的自动化测试&#xff0c;最常见的是 Android 自动化测试&#xff0c;我个人觉得 Android 的测试优先级会更高&#xff0c;也更开放&#xff0c;更容易测试&#xff1b;而 iOS 相较于 Android 要安全稳定的多&#xff0c;但也是一个必须测试的方向&#xff0c;这个系列文…

Android实现雪花特效自定义view

一、前言 这个冬天&#xff0c;老家一直没有下雨&#xff0c; 正好圣诞节&#xff0c;就想着制作一个下雪的特效。 圣诞祝福&#xff1a;平安夜&#xff0c;舞翩阡。雪花飘&#xff0c;飞满天。心与心&#xff0c;永相伴。 圣诞节是传统的宗教节日&#xff0c;对于基 督徒&…

前端自学你还在浪费时间吗?

其实最主要不是学的过程&#xff0c;而是学完后&#xff0c;你有没有把今天的练习题自己在重新敲个2&#xff0c;3遍&#xff0c;这样印象就会更加深刻&#xff0c;以后自己写代码的时候也会更加的得心应手。 手抄笔记让我打好了HTML基础和良好的CSS能力&#xff0c;当然这不一…

Cesium打包入门(gulp与esbuild)

本文针对Cesium源码包的打包工具gulp和esbuild进行了初步探讨&#xff0c;属于入门篇。 首先简要介绍采用gulpesbuild如何为多个源代码文件打包成一个单独文件&#xff0c;然后介绍了下Cesium中的源码包的结构&#xff0c;并简要分析了其打包的相关函数。 本文编译环境IDE使用…

【并发编程学习】一、线程的基本认识

一、线程的基本认识 1.1线程的基本介绍 线程是什么&#xff1f; 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运行单位。 为什么会有多线程&#xff1f; ①因为其是CPU的最小调度单位&#xff0c;所以在多核CPU中&#xff0c…

圣诞节,深圳街头有点冷清了~

正文大家好&#xff0c;我是bug菌~今天是圣诞节&#xff0c;这也是我新冠康复的第二周了吧&#xff0c;还有点小咳嗽&#xff0c;伴随有点鼻炎&#xff0c;不过这周已经上了三天班了&#xff0c;整体感觉还算好吧&#xff0c;毕竟我嘴巴不硬&#xff0c;也比较低调不嚣张&#…

底层硬件创新夯实算力、应用创新贴近业务:英特尔至强助力下的VR医疗培训系统

早在1935年&#xff0c;科幻小说家斯坦利温鲍姆的小说《皮格马利翁的眼镜》中&#xff0c;就构想了一款实现虚拟现实&#xff08;VR&#xff09;的眼镜。近年来&#xff0c;除游戏、娱乐等大众熟知的应用场景外&#xff0c;VR逐渐涉足医疗、教育、生产制造等各种领域。 以医疗…