原文地址:http://marcio.io/2015/07/singleton-pattern-in-go/  

  最近几年go语言的增长速度非常惊人,吸引着各界人士切换到Go语言。最近有很多关于使用Ruby语言的公司切换到Go、体验Go语言、和Go的并行和并发解决问题的文章。
  过去10年里,Ruby on Rails已经让众多的开发者和初创公司快速开发出强大的系统,大多数时候不需要担心他的内部是如何工作的,或者担心线程安全和并发。RoR程序很少创建线程和并行的运行一些东西。整个托管的基础建设和框架栈使用不同的方法,通过多个进程来进行并行。最近几年,像Puma这样的多线程机架式服务器开始流行,但是即使是这样,刚开始也带来了很多关于使用第三方gems和其他没有被设计为线程安全的代码的问题。
     现在有很多开发者开始使用Go语言。我们需要仔细研究我们的代码,并观察代码的行为,需要以线程安全的方式代码设代码。

常识性错误

   最近,我在很多Github库里看到这种类型的错误,单例模式的实现没有考虑线程安全,下面是常识性错误的代码

  

package singleton

type singleton struct {
} var instance *singleton func GetInstance() *singleton {
if instance == nil {
instance = &singleton{} // <---非线程安全的
}
return instance
}

  

  上面的示例中,多个go routines 会进行第一次检查并且都会创建 singleton类型的实例并且互相覆盖。不能保证哪一个实例会被返回,在这个实例上更进一步的操作可能和开发者所期望的不一至。
  这样是有问题的,因为如果对这个单例的实例已经在代码中被应用,可能会有潜在的多个这个类型的实例,并用有各自的状态,产生潜在的不同的代码行为。他也可能成为高度时的恶梦,并且很难定位错误,因为在debug时由于运行时暂停减少潜在的非线程安全的执行而不会真正出现错误,很容易隐藏开发者的问题。

激进的锁

  我也看到一些使用糟糕的方法来解决线程安全的问题。事实上他解决了多线程的问题,但是创造了其他潜在的更严重的问题,他通过对整个方法执行锁定来引入线程竞争

var mu Sync.Mutex

func GetInstance() *singleton {
mu.Lock() // <--- 如果实例已经被创建就没有必要锁写
defer mu.Unlock() if instance == nil {
instance = &singleton{}
}
return instance
}
  上面的代码,我们可以看到,通过引入Sync.Mutex来解决线程安全的问题,并且在创建单例实例前获取锁。问题在于当我们不需要的时候例如,实例已经被创建的时候,只需要返回缓存的单例实例,但是呢也会执行锁操作。在高并发代码基础上,这会产生瓶颈,因为在同一时间只有一个go routine可以得到单例的实例。
     所以这不是最好的方法,我们找找其他的解决方案。

Check-Lock-Check 模式

   在c++和其他语言,用于保证最小锁定并且保证线程安全的最好、最安全的方式是当需要锁定时使用众所周知的Check-Lock-Check模式。下面的伪代码说明了这个模式的大概样子

if check() {
lock() {
if check() {
// perform your lock-safe code here
}
}
}
  这个模式背后的想法是想一开始就检查。用于减少任何激进的锁定。因为一个IF语句比锁定便宜的多。第二我们想等待并获取排他锁所以的块内同一时间只能有一个执行,但是在第一次检察和和排他锁获取之间可能会有其他线程想要获取锁,因此我们需要在块内再次的检查以避免单例实例被其他实例替换。
     多年来,和我一起工作人的熟知这一点,在代码审过程中,这个模式和线程安全思想方面,我对团队非常严厉。
     如果我们应用这个模式到我的GetInstance()方法,我们需要做的如下 :
func GetInstance() *singleton {
if instance == nil { // <-- 不够完善. 他并不是完全的原子性
mu.Lock()
defer mu.Unlock() if instance == nil {
instance = &singleton{}
}
}
return instance
}
  
  这是一个挺好的方法,但是并不完美。因为编译器优化,但是没有实例保存的状态的原子性检查。全面的技术考虑,这并不是安美的。但是已经比之前的方法好多了。   
  但是使用 sync/atomic 包,我们可以原子性的加载和设置标识指示是否已经初始化了我们的实例。
import "sync"
import "sync/atomic" var initialized uint32
... func GetInstance() *singleton { if atomic.LoadUInt32(&initialized) == {
return instance
} mu.Lock()
defer mu.Unlock() if initialized == {
instance = &singleton{}
atomic.StoreUint32(&initialized, )
} return instance
}

  但是.....我相信我们可以通过查看Go语言和标准库的源码看一下go routines 同步的实现方式来做的更好

 Go惯用的单例方法

   我们想要使用Go的惯用手法来实现这个单例模式。所以我们需要看一下打包好的sync标准库。我们找到了 Once 类型。这个对象可以精确的只执行一次操作,下面就是Go标准库的代码

// Once is an object that will perform exactly one action.
type Once struct {
m Mutex
done uint32
} // Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == { // <-- Check
return
}
// Slow-path.
o.m.Lock() // <-- Lock
defer o.m.Unlock()
if o.done == { // <-- Check
defer atomic.StoreUint32(&o.done, )
f()
}
}

  这意味着我们可以运用非常棒的 Go sync包来调用一个只执行一次的方法。因此,我们可以向下面这样调用 once.Do() 方法

once.Do(func() {
// 执行安全的初始化操作
})

  下面你可以看到使用sync.Once类型实现的单例实现的完整代码,用于同步访问GetInstance() 并保证我们的类型初始化只执行一次。

package singleton

import (
"sync"
) type singleton struct {
} var instance *singleton
var once sync.Once func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
  因此,使用sync.Once包是一个完美的安全的实现方式,这种方式有点像Object-C和Swift(Cocoa)的实现dispatch_once 方法用于执行类似的初始化操作。

总结

     当涉及到并行和并发代码时,需要详细检查你的代码。始终让你的团队成员进行代码审查,因此对于这样的事情才能更容易的监督。
     所有的切换到Go语言的新开发者,需要明确的理解线程安全的原理才能更好的改善你的代码。即使Go语言本身通过做了很多努力允许你使用很少的并发知识来设计并发代码。仍有一些语言无法帮你处理的一些情况,你依然需要在开发代码时应用最佳的实践方法

Go 单例模式[个人翻译]的更多相关文章

  1. Go语言中的单例模式(翻译)

    在过去的几年中,Go语言的发展是惊人的,并且吸引了很多由其他语言(Python.PHP.Ruby)转向Go语言的跨语言学习者. Go语言太容易实现并发了,以至于它在很多地方被不正确的使用了. Go语言 ...

  2. Go语言之路—博客目录

    Go语言介绍 为什么你应该学习Go语言? 开发环境准备 从零开始搭建Go语言开发环境 VS Code配置Go语言开发环境 Go语言基础 Go语言基础之变量和常量 Go语言基础之基本数据类型 Go语言基 ...

  3. [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...

  4. 【白话设计模式四】单例模式(Singleton)

    转自:https://my.oschina.net/xianggao/blog/616385 0 系列目录 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factor ...

  5. Java中的GOF23(23中设计模式)--------- 单例模式(Singleton)

    Java中的GOF23(23中设计模式)--------- 单例模式(Singleton) 在Java这这门语言里面,它的优点在于它本身的可移植性上面,而要做到可移植的话,本身就需要一个中介作为翻译工 ...

  6. 【Java】Java 深入探讨 单例模式的实现

    在GoF的23种设计模式中,单例模式是比较简单的一种.然而,有时候越是简单的东西越容易出现问题.下面就单例设计模式详细的探讨一下.   所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在 ...

  7. 深入Java单例模式【转载】

    在GoF的23种设计模式中,单例模式是比较简单的一种.然而,有时候越是简单的东西越容易出现问题.下面就单例设计模式详细的探讨一下.   所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在 ...

  8. spring aop配置文档部分翻译

    欢迎转载交流: http://www.cnblogs.com/shizhongtao/p/3476973.html 下面的文字来自官方文档的翻译,具体事例以后奉上. Advisors "ad ...

  9. Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)

    # 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译) - 原文 <https://githu ...

随机推荐

  1. 团队作业八-Beta版本冲刺计划及安排

    Beta版本冲刺计划及安排 目录: 1.介绍小组新加入的成员,他担任的角色 2.下一阶段需要改进完善的功能 3.下一阶段新增(或修改)的功能 4.需要改进的团队分工 5.需要改进的工具流程 6.冲刺的 ...

  2. 201521123067 《Java程序设计》第4周学习总结

    201521123067 <Java程序设计>第4周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. ●总结: (1)在 ...

  3. python学习笔记1.2

    在python中%的用处是求余数,而不是除数.

  4. 201521123036 《Java程序设计》第14周学习总结

    本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 书面作业 MySQL数据库基本操作 1.1 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) ...

  5. Java课设(学生信息管理系统)

    1.团队课程设计博客链接 http://www.cnblogs.com/Min21/p/7064093.html 2.个人负责模板或任务说明 设计登陆界面和学生信息界面的设计,学生信息的显示.退出等功 ...

  6. 201521123048 《java程序设计》 第11周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) 1. ...

  7. JAVA课程设计-----加减法测试博客

    1.团队成员介绍(一个人做的) 谢季努:网络1513 201521123079 2.项目git地址 3.项目git提交截图 4.项目运行截图 输入答案后点击确认就会出现本次的得分 如果觉得成绩不理想点 ...

  8. 201521123079《java程序设计》第9周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己 ...

  9. Java第十一周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1.互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问) ...

  10. 通过SDK和API获取阿里云RDS的监控数据

    阿里云的RDS自带的监控系统获取数据不怎么直观,想要通过API获取数据通过zabbix显示,因为网上资料缺乏和其他一些原因,获取API签名很困难,但使用阿里云的SDK可以完美避开获取签名的步骤. 阿里 ...