无辜的goroutine

简介:

本文主要是针对一些对于goroutine的“指控”提出我自己的看法,特别是轩脉刃的一篇博客文章《论go语言中goroutine的使用》提出了goroutine的几宗罪。实际上goroutine确实有增加程序复杂度而容易导致问题之处,特别是死锁;但是另外的一些指控,我认为实际上goroutine是没有直接责任的。

以下就《论go语言中goroutine的使用》的内容一一提出我的观点。

第一个指控:goroutine的指针传递是不安全的

原文:

fun main() {

request := request.NewRequest() //这里的NewRequest()是传递回一个type Request的指针

go saveRequestToRedis1(request)

go saveReuqestToRedis2(request)

select{}

}

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中的每行代码来确定是否有修改共享数据??这在实际项目开发过程中是多么降低开发速度的一件事情啊!

我的观点:

1.函数和调用者直接必须遵循一定的规范或者说约定。这个约定包括:

1) 函数签名。这个在强类型的编程语言中可以由编译器保证。

2) 语义。就是调用者要根据函数的用途去调用函数,函数也必须实现自己的目的。如果一个读函数Read实际执行的却是Write操作,就是语义错误。

3) 附带数据的权限控制,包括读写权限和线程(goroutine)安全性。比如参数或者返回的数据由谁来负责控制,函数是不是可以写参数所指向的数据等等。特别在package暴露出来的函数中,对数据的权限说明就特别重要。一个例子是bytes.Buffer.Next方法,在文档中很明确地说明它返回的数据只在下次读写操作前有效。

2.根据1的原则来看上面的例子,就发现saveRequestToRedis1和调用者之间的调用约定并不明确。如果调用约定说明参数指向的数据会被修改,就是调用者的问题;如果调用约定说明参数指向的数据不会被修改,就是函数实现的问题。

3.因此,本例子中的问题实际上是调用约定不明确或者没有遵守的问题,goroutine在这里是无辜的。

第二个指控: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要花费多少时间。

我的观点:

1.其实这个问题和第一个指控是类似的,实际问题还是关于数据的权限和goroutine安全性的约定不明确,只是现在是函数调用其他子函数从而本身变成调用者而已。

2.关于goroutine安全性举个例子:database/sql.Stmt的文档说明就有指出“Stmt is safe for concurrent use by multiple goroutines”。

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语言的并发复杂和繁琐地多,因此我们在使用之前会深思,考虑使用并发获得的好处和坏处。go呢?几乎不。

我的观点:

1.对于package暴露出来的函数,必须在文档(注释)中明确写明函数的调用约定。go标准库就是个比较好的榜样。

2.对于package内部的函数,不需要很明确地写出调用约定。如果是多人开发同一个package,则开发人员有责任去了解被调用函数的默认约定(通过查看函数实现或者简单的约定说明)。

3.在遵守函数约定的前提下,使用goroutine完全不是问题。举个例子:

假设要实现一个排序函数sort,约定是线程不安全的,即不允许把同一个数列在多个goroutine中同时排序。但是我们仍然可以在函数内部使用goroutine:

func sort(numbers []int) {

var wg sync.WaitGroup for i := 0; i < 5; i++ {

wg.Add(1) go func() { // 排序子数组 wg.Done() }()

} wg.Wait() // 合并子数组

}

结论:

1.一些看似由goroutine导致的问题其实不应该归咎于goroutine,那些问题可能是由于不遵守函数调用约定导致的;即使在C/C++里,不遵守函数调用约定一样会导致问题。

2.packge的导出函数特别需要明确函数调用约定,否则会导致调用者误用;而packge内部的函数约定,则需要开发者自己把控(类比于C++中开发者对类的内部函数的责任)。

3.goroutine会导致问题往往是死锁等待等多线程中容易发生的问题。这可以从设计一个良好的设计和良好的代码框架来减少问题的风险,加强代码评审也是一个重要的措施。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-02-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

Kotlin 与 Java 8 的重要新特性以及 Java 9、10 的发展规划Java 9 新特性 极简介绍Java 10/X 的发展规划Kotlin《Kotlin极简教程》正式上架:

Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。我们来一一回顾一下这些特性。

712
来自专栏葡萄城控件技术团队

C#开发人员应该知道的13件事情

本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助。 1. 开发过程 开发过程是错误和缺陷开始的地方。使用工具可以帮助你在发布之后,解决掉...

2249
来自专栏Golang语言社区

无辜的goroutine

简介: 本文主要是针对一些对于goroutine的“指控”提出我自己的看法,特别是轩脉刃的一篇博客文章《论go语言中goroutine的使用》提出了gorout...

3045
来自专栏Python入门

你还在为Python中文乱码而感到烦恼?今天老司机给你讲讲!

有没有遇到过这样的问题,读取文件被提示“UnicodeDecodeError”、爬取网页得到一堆乱码,其实这些都是编码惹的祸,如果不能真正理解编码的问题所在,就...

1663
来自专栏FreeBuf

逆向工厂(二):静态分析技术

* 本文原创作者:追影人,本文属FreeBuf原创奖励计划,未经许可禁止转载 前言 [逆向工厂]第一章节中介绍了逆向技术的相关基础知识,其中提到逆向的两种形式:...

4488
来自专栏工科狗和生物喵

【计算机本科补全计划】指令:计算机的语言(MIPS) Part3

正文之前 今天学的很尴尬,因为有事情,而且新认识了两个计算机学院的保研大佬,不得不感叹我找的导师之强,第一个去上交的,是被金老师推荐去的,听说是跟了目前亚洲第一...

3218
来自专栏carven

浅谈js的date对象对时间字符串的解析

最近的时间都在开发社团内部的应用–隧道口,虽然只有简单的几个页面,但是依然是遇到了不少坑。 其中 date 的时间处理就是一个。

890
来自专栏玄魂工作室

Hacker基础之Python篇:一、环境安装和基础知识

0x01. 前言 emmmmmmm...你只需知道这是一门用途很广的语言,上到大数据AI,下到Linux运维,都可以使用Python,当然,黑客也用Pyth...

2986
来自专栏ImportSource

必懂的NoSQL理论-Map-Reduce(中)

本文主要内容:分区和归并 上一文:必懂的NoSQL理论-Map-Reduce(上) Partitioning and Combining 分区和归并 在最简单...

3356
来自专栏瓜大三哥

UVM模型(五)之factory机制

UVM模型(五)之factory机制 ? factory其实就是一个宏,当设计set_override等操作时,才有必要去理解factory机制的原理。...

3239

扫码关注云+社区

领取腾讯云代金券