谈谈go语言编程的并发安全

问题起因

在分布式存储开源项目 Weed-FS 中, 我发现了一个地方非并发安全(not concurrency-safety), 所以提交了一个 Weed-FS-PullRequest-75 来进行加锁保护。 简化这个问题如下:

当有一个变量, 有一个 goroutine 会对它进行写操作, 其他 goroutine 对它进行读操作。 是否需要对这个变量进行加锁保护。

我觉得不同goroutine并发读写同一个变量, 需要加锁, 这应该是天经地义的常识。 但是这个 PullRequest 居然出乎意料的被作者反驳了。

作者的理由是: 只有一个 goroutine 在写,其他 goroutine 在读,不需要加锁。

但是这样的观点我实在无法苟同, 因为在我的 C/C++ 开发经验中,这是必然需要加锁的典型场景,一般是使用读写锁。 难道是golang在这个方面有一些牛逼的奇淫巧计所以不需要加锁?

所以我耐不住寂寞开始查阅资料,先是官网上的说法。

go内存模型回顾

这个问题先让我们回顾一下 golang 官网上对于 go 内存模型的建议

Advice Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access. To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages. If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don't be clever.

可以看出其实go的内存模型对于并发安全有两种保护措施。 一种是通过加锁来保护,另一种是通过channel来保护。 前者没什么好说的, 后者其实就是一个线程安全的队列。 在 C/C++ 在多线程编程中经常使用的 BlockingQueue , 几乎每个开源项目都有自己的 BlockingQueue, 其实实现起来并不难,大部分实现都大同小异, 我在自己的常用库里面也自己实现了一个BlockingQueue 。 内部加锁实现。

可能很多人都听说过一个高逼格的词叫【无锁队列】。 都一听到加锁就觉得很low,其实对于大部分程序来说。 根本不需要那些高逼格的技术,该加锁就加锁。 一次加锁的耗时差不多是在几十纳秒, 而一次网络IO都是在毫秒级别以上的。 根本不是一个量级。 特别是在现在云计算时代, 大部分人一辈子都遇不到因为加锁成为性能瓶颈的应用场景。

而且我觉得上面那段Advice里面写意味深长的是最后一句【Don't be clever】, 不要自作聪明。

也就是我的解读是:

go语言编程中, 当有多个goroutine并发操作同一个变量时,除非是全都是只读操作, 否则就得【加锁】或者【使用channel】来保证并发安全。 不要觉得加锁麻烦,但是它能保证并发安全。

不过在 Weed-FS-PullRequest-75 上还是说服不了作者, 所以最后在 go-nuts 邮件群组上面发起了这个话题。

go-nuts 上的讨论

go-nuts 上的讨论,打开链接需要自备梯子。 经过讨论后,验证了我的观点是正确的, 所以作者也放心的 Merge 了我的建议 Weed-FS-PullRequest-79

摘出 go-nuts 上有个关于并发安全问题的好文章:Benign data races: what could possibly go wrong? 。 还有就是

go run/build/test -race

这个命令可以更大概率的复现并发安全问题。 有时候并发安全问题不容易复现。所以即使程序运行正常, 也不能说明就没有并发安全问题。

这让我想起来当时看到酷壳一篇博文疫苗:Java HashMap的死循环的时候, 我心里还嘲笑淘宝的人这么搓, 连非线程安全需要加锁都不知道。

没想到原来这么多人对线程安全(在go里面通常叫并发安全)没有清醒的认识, 不只是这个项目里面,包括在相关技术群里面讨论这个问题的时候, 总是有人以为每次读取数据都是原子行为(我怀疑他们可能不理解原子行为是什么), 以为不加锁导致的问题顶多就是读取的数据是修改前的数据罢了。 其实都是典型的误解。

本文来自:不是我干的

感谢作者:YanyiWu

查看原文:谈谈go语言编程的并发安全

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

原文发表时间:2017-04-13

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

beagle MONO 应用的desktop search

      beagle是linux的desktop search软件,跟winows下的google desktop search类似的东西,它可以搜索各种各...

1987
来自专栏Java学习网

程序员编程的 7 + 1 条小贴士

程序员编程的 7 + 1 条小贴士 1.编码之前想一想 用10分钟,20分钟甚至30分钟的时间来想想你需要什么,想想什么样的设计模式(如果有的话)适合你将要编码...

3028
来自专栏FreeBuf

NSA(美国国安局)泄漏文件深度分析(PART 1)

* 本文原创作者:tom_vodu,本文属FreeBuf原创奖励计划,未经许可禁止转载 一、前言 防火墙是保护内网机器不受网络攻击者侵害的第一道也是非常核心...

2985
来自专栏睿哥杂货铺

Linux 性能诊断:快速检查单(Netflix版)

快速检查单(Quick Reference Handbook,QRH)是飞行员在飞行过程中依赖的重要指导性文件。

4047
来自专栏宏伦工作室

基于itchat实现微信群消息同步机器人

3897
来自专栏编程一生

简单的业务更考验技术--化腐朽为神奇

1162
来自专栏美团技术团队

MyFlash——美团点评的开源MySQL闪回工具

由于运维、DBA的误操作或是业务bug,我们在操作中时不时会出现误删除数据情况。早期要想恢复数据,只能让业务人员根据线上操作日志,构造误删除的数据,或者DBA使...

39612
来自专栏野路子程序员

徒手解剖composer,简单了解其实现过程

2986
来自专栏IT派

10 个技巧,让你更专业地使用 console 进行 JS 调试

首先,我必须承认这一点,我将利用这个平台从我的开发环境中清理出骨架(轮廓)。有时候,我所做的“魔法”(有些人称之为“编码”),并不像我的同事在为他们展示这些宏伟...

1070
来自专栏FreeBuf

挖洞经验 | 看我如何利用上传漏洞在PayPal服务器上实现RCE执行

当你看到这篇文章标题时,是不是很吃惊,PayPal服务器的RCE漏洞?Dafaq?WTF?真的吗?这当然是真的,很幸运,我通过枚举和域名查找方法发现了该漏洞。 ...

3935

扫码关注云+社区

领取腾讯云代金券