前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >你确定没有滥用 goroutine 吗

你确定没有滥用 goroutine 吗

作者头像
阿兵云原生
发布于 2023-02-16 03:34:25
发布于 2023-02-16 03:34:25
29700
代码可运行
举报
文章被收录于专栏:golang云原生newgolang云原生new
运行总次数:0
代码可运行

写在前面

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

并发错误

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 吗?,我们来实际看看效果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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, 看看效果会不会好一点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 查看输出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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 的地址一直是同一个

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的地址又是有何不同

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的情况,若子协程挂掉,那么主协程也会随之挂掉,这里我们需要特别注意

举一个简单的例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 而挂掉

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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 就不会导致 主协程挂掉了,这里谨记

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 !! ")
}

程序运行效果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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 信息打印出来,便于分析问题

来写一个简单的例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的技巧还很多,咱们需要用起来才能够体现它的价值

欢迎点赞,关注,收藏

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

好了,本次就到这里

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 阿兵云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用 Kotlin API 实践 WorkManager
WorkManager 提供了一系列 API 可以更加便捷地规划异步任务,即使在应用被关闭之后或者设备重启之后,仍然需要保证立即执行的或者推迟执行的任务被正常处理。对于 Kotlin 开发者,WorkManager 为协程提供了最佳的支持。在本文中,我将通过实践 WorkManager codelab 为大家展示 WorkManager 中与协程相关的基本操作。那么让我们开始吧!
Android 开发者
2022/03/09
5320
WorkManager: 周期性任务
WorkManager 是一个 Android Jetpack 扩展库,它可以让您轻松规划那些可延后、异步但又需要可靠运行的任务。对于绝大部分后台执行任务来说,使用 WorkManager 是目前 Android 平台上的最佳实践。
Android 开发者
2022/09/23
1.9K0
WorkManager: 周期性任务
[译] WorkManager 基础入门
欢迎来到我们 WorkManager 系列的第二篇文章。WorkManager 是一个 Android Jetpack 库,当满足工作的约束条件时,用来运行可延迟、需要保障的后台工作。对于许多类型的后台工作,WorkManager 是当前的最佳实践方案。在第一篇博文中,我们讨论了 WorkManager 是什么以及何时使用 WorkManager。
Android 开发者
2024/01/26
2150
[译] WorkManager 基础入门
Android Jetpack架构组件(七)之WorkManager
在Android应用开发中,或多或少的会有后台任务的需求,根据需求场景的不同,Android为后台任务提供了多种不同的解决方案,如Service、Loader、JobScheduler和AlarmManger等。后台任务通常用在不需要用户感知的功能,并且后台任务执行完成后需要即时关闭任务回收资源,如果没有合理的使用这些API就会造成电量的大量消耗。为了解决Android电量大量消耗的问题,Android官方做了各种优化尝试,从Doze到app Standby,通过添加各种限制和管理应用程序进程来包装应用程序不会大量的消耗电量。
xiangzhihong
2021/01/06
2.1K0
Jetpack WorkManager 看这一篇就够了~
最近有读者反馈,在我的新书《Android Jetpack 开发:原理解析与应用实战》中并没有提及到WorkManager,这是因为目前这个东西在国内并不是很好用。最近因为工作需要正好研究了下,也作为补充章节分享给读者。
黄林晴
2022/09/19
1.6K0
Jetpack WorkManager 看这一篇就够了~
Android之任务调度WorkManager和JobSchedule的使用
调度任务也是最近产品中需要用的,定时与后台进行数据同步,研究了几种方法后,觉得还是JobSchedule相对效果还好点,主要原因是WorkManager的定时任务最短也需要15分钟,虽然JobSchedule在Android7.0后也这样的,但是可以通过别的办法实现,所以两个都说一下,两个也都会用到。
Vaccae
2021/09/17
3.9K0
自定义 WorkManager —— 基础概念
WorkManager 是一个 Android Jetpack 扩展库,它可以让您轻松规划那些可延后、异步但又需要可靠运行的任务。对于绝大部分后台执行任务来说,使用 WorkManager 是目前 Android 平台上的最佳实践。
Android 开发者
2022/09/23
6380
笔记之Android架构组件-WorkManager
service一直被用来做后台运行的操作,包括一些保活,上传数据之类的,这个后台运行的弊端很多,比如耗电,比如设计用户隐私之类的,谷歌对这些后台行为进行了一些处理,从Android Oreo(API 26) 开始,如果一个应用的目标版本为Android 8.0,当它在某些不被允许创建后台服务的场景下,调用了Service的startService()方法,该方法会抛出IllegalStateException。并且出台了一些新政策:
易帜
2022/02/09
9150
Jetpack组件之WorkManager
Android应用中大部分都需要执行后台任务,因此也提供了多种解决方案,如JobScheduler、Loader等。但不合理的使用这些API,会造成消耗大量电量。JetPack中的WorkManager为应用程序执行后台任务提供了 一个统一的解决方案。 WorkManager可以自动维护后台任务的执行时机,执行顺序,执行状态。
八归少年
2022/06/29
1.2K0
Jetpack组件之WorkManager
AAC---WorkManager
WorkManager目前还在Alpha阶段,还存在一些问题。不过等后续Release后,又是开发的一大助力。
None_Ling
2018/12/27
1K0
[译] WorkManager 基础入门
欢迎来到我们 WorkManager 系列的第二篇文章。WorkManager 是一个 Android Jetpack 库,当满足工作的约束条件时,用来运行可延迟、需要保障的后台工作。对于许多类型的后台工作,WorkManager 是当前的最佳实践方案。在第一篇博文中,我们讨论了 WorkManager 是什么以及何时使用 WorkManager。
Android 开发者
2019/07/08
9930
[译] WorkManager 基础入门
使用 WorkManager 处理需要立刻执行的后台任务
当需要执行长时间运行的任务,而应用处于后台状态时,您会遇到 后台执行限制,该特性是在 Android 8.0 之后增加的。我们鼓励开发者进行行为变更以提升整个平台的用户体验。
Android 开发者
2022/03/09
9600
WorkManager从入门到实践,有这一篇就够了
上一次我们对Paging的应用进行了一次全面的分析,这一次我们来聊聊WorkManager。
Rouse
2019/08/15
1.9K0
Jetpack-WorkManager
今天我们来讲以下google推荐我们使用jetpack进行后台任务处理的组件:workManager。 参考: https://mp.weixin.qq.com/s/OorUNDO3GVHATJrZOijh_A
android_薛之涛
2021/01/18
1.5K0
Android中WorkManager的简单使用
WorkManager是Google推出的一个新组件,用于替代传统的Service进行后台任务处理。它的主要特点包括:
用户2195279
2024/12/11
1760
Android中WorkManager的简单使用
如何使用WorkManager执行后台任务(上)
WorkManager 是 Android Jetpack中的一部分,它主要是封装了 Android 后台任务的调度逻辑。在前文《Android后台任务处理指南》一文中知道,WorkManager 是高级 API,它实际是封装了 JobScheduler, Firebase JobDispatcher, 和 AlarmManager 底层的使用,提供了简单且灵活易用的API,它有很多优势:
阳仔
2019/07/30
1.6K0
如何使用WorkManager执行后台任务(上)
Android WorkManager: 轻松管理后台任务
在Android应用开发中,有效地管理后台任务是至关重要的。Android WorkManager是一个强大的库,旨在简化任务调度和后台工作管理。本文将深入探讨WorkManager的内部实现细节、原理和具体使用。
Rouse
2023/11/27
5600
Android WorkManager: 轻松管理后台任务
JetPack--WorkManager
定义Work类,继承Worker,doWork方法需要返回一个Result的结果,有成功、重试、失败:
aruba
2021/12/06
6680
使用 Dagger 自定义 WorkManager
WorkManager 是一个 Android Jetpack 扩展库,它可以让您轻松规划那些可延后、异步但又需要可靠运行的任务。对于绝大部分后台执行任务来说,使用 WorkManager 是目前 Android 平台上的最佳实践。
Android 开发者
2022/09/23
8040
WorkManager的用法
  绝大部分应用程序都有后台执行任务的需求,根据需求的不同,Android为后台任务提供了多种解决方案,如JobShedule,Loader,Service等。如果这些api没有被正确的使用,则可能导致消耗大量的电量。WorkManager为应用程序中那些不需要及时完成的任务提供了一个统一的解决方案,以便在设备电量和用户体验间达到一个比较好的平衡。WorkManager有三个重要特点,分别如下:
故乡的樱花开了
2023/10/22
4410
相关推荐
使用 Kotlin API 实践 WorkManager
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文