go语言中goroutine的使用详解
go中的goroutine是go语言在语言级别支持并发的一种特性。初接触go的时候对go的goroutine的欢喜至极,实现并发简便到简直bt的地步。
但是在项目过程中,越来越发现goroutine是一个很容易被大家滥用的东西。goroutine是一把双面刃。这里列举一下goroutine使用的几宗罪:
1、goroutine的指针传递是不安全的
fun main() { request := request.NewRequest() //这里的NewRequest()是传递回一个type Request的指针 go saveRequestToRedis1(request) go saveReuqestToRedis2(request) select{} }
非常符合逻辑的代码:
主routine开一个routine把request传递给saveRequestToRedis1,让它把请求储存到redis节点1中
同时开另一个routine把request传递给saveReuqestToRedis2,让它把请求储存到redis节点2中
然后主routine就进入循环(不结束进程)
问题现在来了,saveRequestToRedis1和saveReuqestToRedis2两个函数其实不是我写的,而是团队另一个人写的,我对其中的实现一无所知,也不想去仔细看内部的具体实现。但是根据函数名,我想当然地把request指针传递进入。
实际上saveRequestToRedis1和saveRequestToRedis2 是这样实现的:
func saveRequestToRedis1(request *Request){ … request.ToUsers = []int{1,2,3} //这里是一个赋值操作,修改了request指向的数据结构 … redis.Save(request) return }
这样有什么问题?saveRequestToRedis1和saveReuqestToRedis2两个goroutine修改了同一个共享数据结构,但是由于routine的执行是无序的,因此我们无法保证request.ToUsers设置和redis.Save()是一个原子操作,这样就会出现实际存储redis的数据错误的bug。
好吧,你可以说这个saveRequestToRedis的函数实现的有问题,没有考虑到会是使用go routine调用。请再想一想,这个saveRequestToRedis的具体实现是没有任何问题的,它不应该考虑上层是怎么使用它的。
那就是我的goroutine的使用有问题,主routine在开一个routine的时候并没有确认这个routine里面的任何一句代码有没有修改了主routine中的数据。对的,主routine确实需要考虑这个情况。
主goroutine在启用go routine的时候需要阅读子routine中的每行代码来确定是否有修改共享数据??这在实际项目开发过程中是多么降低开发速度的一件事情啊!
go语言使用goroutine是想减轻并发的开发压力,却不曾想是在另一方面增加了开发压力。
上面说的那么多,就是想得出一个结论:
gorotine的指针传递是不安全的!!
如果上一个例子还不够隐蔽,这里还有一个例子:
fun (this *Request)SaveRedis() { redis1 := redis.NewRedisAddr("xxxxxx") redis2 := redis.NewRedisAddr("xxxxxx") go this.saveRequestToRedis(redis1) go this.saveRequestToRedis(redis2) select{} }
很少人会考虑到this指针指向的对象是否会有问题,这里的this指针传递给routine应该说是非常隐蔽的。
2、goroutine增加了函数的危险系数
这点其实也是源自于上面一点。上文说,往一个go函数中传递指针是不安全的。那么换个角度想,你怎么能保证你要调用的函数在函数实现内部不会使用go呢?如果不去看函数体内部具体实现,是没有办法确定的。
例如我们将上面的典型例子稍微改改
func main() { request := request.NewRequest() saveRequestToRedis1(request) saveRequestToRedis2(request) select{} }
这下我们没有使用并发,就一定不会出现这问题了吧?追到函数里面去,傻眼了:
func saveReqeustToRedis1(request *Request) { … go func() { … request.ToUsers = []{1,2,3} …. redis.Save(request) } }
里面起了一个goroutine,并修改了request指针指向的对象。这里就产生了错误了。好吧,如果在调用函数的时候,不看函数内部的具体实现,这个问题就无法避免。
所以说,从最坏的思考角度出发,每个调用函数理论上来说都是不安全的!试想一下,这个调用函数如果不是自己开发组的人编写的,而是使用网络上的第三方开源代码...确实无法想象找出这个bug要花费多少时间。
3、goroutine的滥用陷阱
看一下这个例子:
func main() { go saveRequestToRedises(request) } func saveRequestToRedieses(request *Request) { for _, redis := range Redises { go redis.saveRequestToRedis(request) } } func saveRequestToRedis(request *Request) { …. go func() { request.ToUsers = []{1,2,3} … redis.Save(request) } }
神奇啊,go无处不在,好像眨眨眼就在哪里冒出来了。这就是go的滥用,到处都见到go,但是却不是很明确,哪里该用go?为什么用go?goroutine确实会有效率的提升么?
c语言的并发比go语言的并发复杂和繁琐地多,因此我们在使用之前会深思,考虑使用并发获得的好处和坏处。
处理方法
下面说几个我处理这些问题的方法:
1、当启动一个goroutine的时候,如果一个函数必须要传递一个指针,但是函数层级很深,在无法保证安全的情况下,传递这个指针指向对象的一个克隆,而不是直接传递指针
fun main() { request := request.NewRequest() go saveRequestToRedis1(request.Clone()) go saveReuqestToRedis2(request.Clone()) select{} }
Clone函数需要另外写。可以在结构体定义之后简单跟上这个方法。比如:
func (this *Request)Clone(){ newRequest := NewRequst() newRequest.ToUsers = make([]int, len(this.ToUsers)) copy(newRequest.ToUsers, this.ToUsers) }
其实从效率角度考虑这样确实会产生不必要的Clone的操作,耗费一定内存和CPU。但是在我看来,首先,为了安全性,这个尝试是值得的。
其次,如果项目对效率确实有很高的要求,那么你不妨在开发阶段遵照这个原则使用clone,然后在项目优化阶段,作为一种优化手段,将不必要的Clone操作去掉。这样就能在保证安全的前提下做到最好的优化。
2、什么时候使用go的问题
有两种思维逻辑会想到使用goroutine:
1 业务逻辑需要并发
比如一个服务器,接收请求,阻塞式的方法是一个请求处理完成后,才开始第二个请求的处理。其实在设计的时候我们一定不会这么做,我们会在一开始就已经想到使用并发来处理这个场景,每个请求启动一个goroutine为它服务,这样就达到了并行的效果。这种goroutine直接按照思维的逻辑来使用goroutine
2 性能优化需要并发
一个场景是这样:需要给一批用户发送消息,正常逻辑会使用
for _, user := range users { sendMessage(user) }
但是在考虑到性能问题的时候,我们就不会这样做,如果users的个数很大,比如有1000万个用户?我们就没必要将1000万个用户放在一个routine中运行处理,考虑将1000万用户分成1000份,每份开一个goroutine,一个goroutine分发1万个用户,这样在效率上会提升很多。这种是性能优化上对goroutine的需求
按照项目开发的流程角度来看。在项目开发阶段,第一种思路的代码实现会直接影响到后续的开发实现,因此在项目开发阶段应该马上实现。
但是第二种,项目中是由很多小角落是可以使用goroutine进行优化的,但是如果在开发阶段对每个优化策略都考虑到,那一定会直接打乱你的开发思路,会让你的开发周期延长,而且很容易埋下潜在的不安全代码。
因此第二种情况在开发阶段绝不应该直接使用goroutine,而该在项目优化阶段以优化的思路对项目进行重构。
推荐:golang开发栏目
以上是go语言中goroutine的使用详解的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Go爬虫Colly中的Queue线程问题探讨在使用Go语言的Colly爬虫库时,开发者常常会遇到关于线程和请求队列的问题。�...

Go语言中用于浮点数运算的库介绍在Go语言(也称为Golang)中,进行浮点数的加减乘除运算时,如何确保精度是�...

Go语言中字符串打印的区别:使用Println与string()函数的效果差异在Go...

Go语言中使用RedisStream实现消息队列时类型转换问题在使用Go语言与Redis...

GoLand中自定义结构体标签不显示怎么办?在使用GoLand进行Go语言开发时,很多开发者会遇到自定义结构体标签在�...

Go语言中哪些库是大公司开发或知名开源项目?在使用Go语言进行编程时,开发者常常会遇到一些常见的需求,�...

Go语言中结构体定义的两种方式:var与type关键字的差异Go语言在定义结构体时,经常会看到两种不同的写法:一�...
