一个commit引发的思考
这几天我翻了翻golang的提交记录,发现了一条很有意思的提交:bc593ea,这个提交看似简单,但是引人深思。
commit讲了什么
commit的标题是“sync: document implementation of Once.Do”,显然是对文档做些补充,然而奇怪的是为什么要对某个功能的实现做文档说明呢,难道不是配合代码+注释就能理解的吗?
根据commit的描述我们得知,Once.Do的实现问题在过去几个月内被问了至少两次,所以官方决定澄清:
It's not correct to use atomic.CompareAndSwap to implement Once.Do,
and we don't, but why we don't is a question that has come up
twice on golang-dev in the past few months.
Add a comment to help others with the same question.
不过这不是这个commit的精髓,真正有趣的部分是添加的那几行注释。
有趣的疑问
commit添加的内容如下:

乍一看可能平平无奇,然而仔细思考过后,我们就会发现问题了。
众所周知,sync.Once用于保证某个操作只会执行一次,因此我们首先考虑到的就是为了并发安全加mutex,但是once对性能有一定要求,所以我们选用原子操作。
这时候atomic.CompareAndSwapUint32很自然的就会浮现在脑海里,而下面的结构也很自然的就给出了:
func (o *Once) Do(f func()) {
if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
f()
}
}
然而正是这种自然联想的方案却是官方否定的,为什么?
原因很简单,举个例子,我们有一个模块,使用模块里的方法前需要初始化,否则会报错:
module.go:
package module
var flag = true
func InitModule() {
// 这个初始化模块的方法不可以调用两次以上,以便于结合sync.Once使用
if !flag {
panic("call InitModule twice")
}
flag = false
}
func F() {
if flag {
panic("call F without InitModule")
}
}
main.go:
package main
import (
"module"
"sync"
"time"
)
var o = &sync.Once{}
func DoSomeWork() {
o.Do(module.InitModule()) // 不能多次初始化,所以要用once
module.F()
}
func main() {
go DoSomeWork() // goroutine1
go DoSomeWork() // goroutine2
time.Sleep(time.Second * 10)
}
现在不管goroutine1还是goroutine2后运行,module都能被正确初始化,对于F的调用也不会panic,但我们不能忽略一种更常见的情况:两个goroutine同时运行会发生什么?
我们列举其中一种情况:
- goroutine1先运行,这时如果按我们所想的once实现,CAS操作成功,
InitModule开始执行 - 这时goroutine2也在运行,但CAS因为别的routine操作成功,这里返回失败,
InitModule执行被跳过 - Once.Do返回就意味着我们需要的操作已经被执行,这时goroutine2开始执行
F() - 但是我们的
InitModule在goroutine1中因为某些原因没执行完,所以我们不能调用F - 于是问题发生了
你可能已经看出问题了,我们没有等到被调用函数执行完就返回了,导致了其他goroutine获得了一个不完整的初始化状态。
解决起来也很简单:
- 我们先判断执行标志,如果已经执行过就直接返回
- 因为是判断执行标志而不修改,就会有多个routine同时判断位true的情况,我们用mutex原子化对被调用函数
f的操作 - 获得mutex之后先检查执行标志,以免重复执行
- 接着调用
f - 然后我们把执行标志设置为1
- 最后解除mutex,当其他进入判断的routine重复上述过程时就能保证
f只会被调用一次了
这是代码:
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
结束语
从这个问题我们可以看到,并发编程其实并不难,我们给出的解决方案是相当简单的,然而难的在于如何全面的思考并发中会遇到的问题从而编写并发安全的代码。
golang的这个commit给了我们一个很好的例子,同时也是一个很好的启发。
一个commit引发的思考的更多相关文章
- Spring之LoadTimeWeaver——一个需求引发的思考---转
原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...
- 由一个emoji引发的思考
由一个emoji引发的思考 从毕业以来,基本就一直在做移动端,但是一直就关于移动端的开发,各种适配问题的解决,在日常搬砖中处理了就过了,也没有把东西都沉淀下来,觉得甚是寒颜.现就一个小bug,让我们来 ...
- 从一个聊天信息引发的思考之Android事件分发机制
转载请声明:http://www.cnblogs.com/courtier/p/4295235.html 起源: 我在某一天看到了下面的一条信息(如下图),我想了下(当然不是这 ...
- vmware中如何检查cpu的使用状况-一个考题引发的思考
来自一个VCP的考题,有点兴趣.可参看: 如何在VMware里使用esxtop? http://thocm.com/a/caozuoxitongzixun/xunihuazonghezixun/VMw ...
- MyBatis 学习记录7 一个Bug引发的思考
主题 这次学习MyBatis的主题我想记录一个使用起来可能会遇到,但是没有经验的话很不好解决的BUG,在特定情况下很容易发生. 异常 java.lang.IllegalArgumentExceptio ...
- 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...
- 一个小BUG引发的思考。(论开发与测试之间的那点事)
标题不是“一个馒头引发的血案”. 言归正传:今天上午测试的时候,发现了一个BUG,如图: 一个用肉眼就能发现的BUG.原因当然是因为开发同事没有自测试,流入到了测试人员这里了. 无非是开发同事不严谨造 ...
- SQLAlchemy并发写入引发的思考
背景 近期公司项目中加了一个积分机制,用户登录签到会获取登录积分,但会出现一种现象就是用户登录时会增加双倍积分,然后生成两个积分记录.此为问题 问题分析 项目采用微服务架构,下图为积分机制流程 ...
- 由SecureCRT引发的思考和学习
由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...
随机推荐
- 机器学习:DeepDreaming with TensorFlow (三)
我们看到,利用TensorFlow 和训练好的Googlenet 可以生成多尺度的pattern,那些pattern看起来比起单一通道的pattern你要更好,但是有一个问题就是多尺度的pattern ...
- UVA 10641 - Barisal Stadium(DP + 几何)
题目链接:10641 - Barisal Stadium 题意:逆时针给定n个点,在给m个灯,每一个灯有一个花费,要求最小花费使得全部边能被灯照到 思路:用向量叉积推断向量的顺逆时针关系,从而预处理出 ...
- 使用 LaTex 制作个人简历(CV,英文版)
\documentclass[12pt]{article} \textwidth=6.5in \textheight=9in \topmargin=-1.1in \headheight=0in \he ...
- python短信轰炸机版本smsbomb----------部分(post)
用一些用手机号注冊且须要发送验证码的站点的漏洞.能够向不论什么人的手机号发送短信,当然短信内容,我们无法控制.所以主要工作还是寻找这种站点.然后利用Fiddler或者HttpWatch分析请求.使用p ...
- Oracle 已有则更新,没有则插入
使用merge merge into 表名 t1 using (select '数据数据' 字段1,'数据数据' 字段2 from dual) t2 on (t1.字段1 = t2.字段1) when ...
- 【转】opencart 源码解析
前台控制程序列表-catalog/controller Catalog|controller|account 会员功能 |—— account.php 会员功能主頁|—— address.php 会员 ...
- datacontract helper
public static class DataContractHelper { public static void ToDCFile<T>(this T obj, string pat ...
- Win10版《芒果TV》获评2016年度Windows Store最佳官方/休闲娱乐应用(LiveSino和微软信仰中心联合评选)
微软信仰中心于2016年12月9日联合了 LiveSino 进行了最佳 Windows Store 应用特辑的投票评选,通过为期20天的海量用户投票,Win10版<芒果TV>荣获最佳官方应 ...
- Win10商店芒果TV UWP版更新,新增后台视频下载
湖南卫视旗下唯一官方视频平台<芒果TV>近日向Win10商店提交了芒果TV UWP V3.0.0版,这次为广大Win10平台用户带来了期待已久的重大功能和更新,可谓是良心厂商,值得鼓励和支 ...
- WPF 添加外部ResourceDirectory
如果Resource资源文件在程序集中,可直接如下将资源文件添加当当前运行时 Application.Current.Resources.MergedDictionaries.Add(new Reso ...