golang atomic load 性能_设计模式之Golang单例模式 - Go语言中文社区

golang atomic load 性能_设计模式之Golang单例模式


973565033acc50405c1fba988fde2d5e.gif

今天给大家讲下什么是单例模式,以及在Go语言中如何用正确的姿势实现它。其实单例模式是一种在平时开发中经常用到的软件设计模式。在设计模式结构中,其核心是只包含一个被称为单例的特殊类。通过单例模式可以确保系统中一个类只有一个实例,且该实例容易被外界访问,从而方便对实例数量的控制并节约系统资源。

1. 懒汉模式

懒汉模式是平时软件开发中比较常见的,也是用的最多的一种,该模式的最大缺点就是非线程安全。什么是非线程安全呢,这里就不展开讲了,后续会单独拿一期来讲什么是线程安全和非线程安全。

懒汉模式代码示例如下:

// 定义单例的结构体type Singleton struct {}// 声明一个Singleton结构体指针的变量var singleton *Singleton// 获取单例的函数,返回Singleton结构体指针类型func GetSingleton() *Singleton {  // 如果为空,则创建单例  if singleton == nil {    singleton = &Singleton{}  }  return singleton}
2. 带普通锁的单例模式

上面讲的懒汉模式,其实是没有带锁的,也就是并发处理时会发生竞争,从而会导致程序出错退出。

普通锁可以解决并发处理带来的问题,这里说的普通锁其实是使用了Go的sync.Mutex,其缺点就是要人工维护锁的添加和释放,在有些时候对锁的使用不当的话,反而会带来不必要的性能消耗,事倍功半。 其工作原理类似于Linux内核的futex对象,具体实现原理这里就不详细展开讲来,sync.Mutex的原理后面也会单独用一期来讲。

带普通锁的单例模式示例代码如下:

// 定义单例的结构体type Singleton2 struct {}// 声明一个Singleton结构体指针的变量var instance *Singleton2// 声明普通锁变量,muvar mu sync.Mutex// 获取带锁的单例函数func GetInstance() *Singleton2 {  // 加锁  mu.Lock()  // 函数退出前解锁  defer mu.Unlock()  // 如果为空,则创建单例  if instance == nil {    instance = &Singleton2{}  }  return instance}
3. 比较优雅的单例模式

以上两钟单例模式都有缺点,不是那么完美,而Golang本身sync.Once库里就已经很好的实现了单例模式。

示例代码如下:

// 创建一个结构体type Manager struct {}// 声明两个全局变量,一个是Manager结构体指针,一个是用于单例等nocevar m *Managervar once sync.Once// 创建Manager单例函数func GetManage() *Manager {  // once.Do已经优雅的封装好了部分加锁的代码  once.Do(func() {    m = &Manager{}  })  return m}

简单来说,sync.Once表示只执行一次函数,要做到这点,需要有两个条件:

  • 计数器,统计函数执行的次数

  • 线程安全,保证在多个goroutine(并发)情况下,函数仍然只执行一次,这里面其实也涉及到锁

其中sync.Once实现的核心函数就是Do(),看一下Do函数的源码,如下:

// Once源码的数据结构type Once struct {   m    Mutex   done uint32}// Do函数实现func (o *Once) Do(f func()) {   if atomic.LoadUint32(&o.done) == 1 {      return   }   o.m.Lock()   defer o.m.Unlock()   if o.done == 0 {      defer atomic.StoreUint32(&o.done, 1)      f()   }}

根据以上的源码可以看到是定义了Once结构体,其中的done成员就是用于统计函数执行的次数的,m成员是锁,保障线程安全的。

Do方法也比较简单:

  1. 首先原子操作load函数执行次数,如果已经执行过了,就return

  2. 然后lock加锁

  3. 执行函数,原子操作store函数执行次数,赋值为1

  4. 解锁unlock

如果换做是我们来写的话,一般就会先直接加锁,然后再比较函数执行的次数,而这里的源码用了原子操作,这样可以提高性能。总的来说,一些标志位可以通过原子操作来表示,从而避免加锁,提高性能。

以上完整示例代码已归档到我的github,如有需要欢迎下载学习交流:https://github.com/Scoefield/gokeyboardman/tree/main/singletonmode

本期设计模式之Golang单例模式的介绍就到这里啦,感谢您的阅读,如有疑问或意见请及时反馈给我们。


上一篇文章:

Golang中的“包”

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_33936345/article/details/112099227
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-06-12 20:27:13
  • 阅读 ( 1040 )
  • 分类:设计模式

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢