原文地址: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】 第一次Daily Scrum Meeting

    一.本次会议为第一次meeting会议 二.时间:20::0AM-20:50AM 地点:宿舍楼下 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳   对已完成的功能进行进一步测 ...

  2. java--利用exe4j生成.exe的可执行文件

    工具:eclipse,exe4j,jre,这三个都可以直接在官方网站下载,下面所用到的都是最新版的. 前期准备:用eclipse编好需要生成.exe文件的project,另外exe4j需要一个注册码, ...

  3. bean的生命周期以及延迟实例化

    可以指定bean的初始化创建的时候调用的方法,以及销毁的时候调用的方法. 通过指定中的init-method和destroy-method方法指定bean的创建和销毁的时候执行类中的方法. 把lazy ...

  4. 201521123008《Java程序设计》第六周实验总结

    1.本周学习总结 2.书面作业 1.clone方法 1.1 Object对象中的clone方法是被protected修饰,在自定义的类中覆盖clone方法时需要注意什么? 因为被protected修饰 ...

  5. 201521123011 《Java程序设计》 第三周学习总结

    1. 本周学习总结 2. 书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; p ...

  6. 201521123101 《Java程序设计》第1周学习总结

    1. 本周学习总结 在学习Java之前要做好准备工作,了解Java从研发后开始如何一步步完善,其与C++.C语言的异同,然后下载JDK.Eclipse.Notepad等软件,以便于未来的学习. 2. ...

  7. Eclipse rap 富客户端开发总结(10) : Rap不同系统间的差异和处理方式

    平常进行 rap 程序开发一般都是在 win 下面完成 , 然后在 tomcat 下面测试 , 但是程序最终发布一般都是在 linux  aix 上面 , 这个时候就有能会出现一下问题,下面 2 个问 ...

  8. Spring - bean的依赖关系(depends-on属性)

    depends-on是bean标签的属性之一,表示一个bean对其他bean的依赖关系.乍一想,不是有ref吗?其实还是有区别的,<ref/>标签是一个bean对其他bean的引用,而de ...

  9. OpenShift中的持续交付

    上一文中讲述了如何在AWS下搭建OpenShift集群.这篇文章将目光转向如何在OpenShift中实现CI/CD以及产品环境的部署. 持续交付 如果要打造一个持续交付的流水线,首先要考虑多环境的问题 ...

  10. C++临时对象以及针对其进行的优化

    C++临时对象以及针对其进行的优化 C++中真正的临时对象是看不见的,它们不出现在你的源代码中. 那么什么时候回产生临时对象呢?主要是三个时刻: 产生临时对象的三个时刻: 用构造函数作为隐式类型转换函 ...