Go Singleton

JunChow520 · · 487 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

单例模式

单例模式(单子模式)是常用的软件设计模式,其核心结构中只包括一个被称之为单例的特殊类,通过单例模式可以保证系统中一个类有且仅有一个实例,同时该实例化需要易于外界访问,从而方便对实例个数的控制来节约系统资源。

  • 单例对象的类型必须保证只有一个实例存在,全局具有唯一接口访问。
  • 单例模式具有两种加载方式分别是懒汉加载和饿汉加载

懒汉加载(Lazy Loading)

懒汉加载通俗来讲,就是创建对象是比较懒,先不急着创建对象,而是在需要加载配置文件时再去创建。

  • 懒汉模式是指全局的单例实例只会在第一次被使用时才会被构建
  • 懒汉模式的缺点在于非线程安全,由于在创建时可能存在线程访问,因此有可能出现多个实例。
type Singleton struct{}
//私有属性
var singleton *Singleton
//共有方法
func GetInstance() *Singleton {
    if singleton == nil {
        singleton = &Singleton{}//非线程安全
    }
    return singleton
}

代码说明

  • 构建一个单例结构体
  • 设置一个私有变量作为每次需要返回的单例
  • 设置一个可以获取单例的共有方法对外暴露

饿汉加载

  • 饿汉模式是指全局的单例实例在类装载时才会被构建
  • 饿汉模式的缺点在于单例实例初始化可能会比较耗时,因此加载时间会延长。
type Singleton struct{}

var singleton *Singleton = &Singleton{}

func GetInstance() *Singleton {
    return singleton
}

饿汉模式会在包加载的时候就会创建单例对象,如果程序中不使用该对象则会持续占用在内存中,虽然和懒汉加载相比更加安全,但会减慢程序启动速度。

懒汉加锁

由于懒汉模式是非线程安全,可通过加锁来解决并发时的线程安全,一般使用互斥锁来解决可能出现的数据不一致问题,但每次加锁也需付出性能代价。

type Singleton struct{}

var (
    singleton *Singleton
    locker    sync.Mutex
)

func GetInstance() *Singleton {
    locker.Lock()
    defer locker.Unlock()

    if singleton == nil {
        singleton = &Singleton{}
    }

    return singleton
}

每次请求单例时都会加锁和解锁,锁的目的在于解决对象初始化时可能出现的并发问题,当对象被创建之后,实际上加锁已经失去了意义,此时会拖慢速度。

使用Golang的sync.Mutex,其工作模式类似于Linux内核中的futex对象,初始化时填入的0值将mutext设置为未锁定状态,同时会保证时间开销最小。

懒汉双重锁(Check-lock-Check)

为解决重复加锁和解锁的问题,可引入双重锁检查机制(Check-lock-Check),又称为DCL(Double Check Lock)。即第一判断时不加锁,第二次判断时加锁以保证线程安全,一旦对象建立则获取对象时就无需加锁了。

为避免懒汉模式每次都需加锁带来的性能损耗,可采用双重锁来避免每次加锁以提高代码效率,即带检查所的单例模式。

type Singleton struct{}

var (
    singleton *Singleton
    locker    sync.Mutex
)
//只有当对象未被初始化时才会加锁和解锁,每次访问都需要检查两遍。
func GetInstance() *Singleton {
    if singleton == nil {//单例未实例化才会加锁
        locker.Lock()
        defer locker.Unlock()

        if singleton == nil {//单例未实例化才会创建
            singleton = &Singleton{}
        }
    }

    return singleton
}

双重锁的缺陷在于编译器优化不会检查实例的存储状态,同时每次访问都需要检查两次,为解决这个问题,可采用sync/atomic包中原子性操作来自动加载并设置标记。

import (
    "sync"
    "sync/atomic"
)

type Singleton struct{}

var (
    instance    *Singleton
    initialized uint32
    locker      sync.Mutex
)

func GetInstance() *Singleton {
    if atomic.LoadUint32(&initialized) == 1 {//一次判断即可返回
        return instance
    }

    locker.Lock()
    defer locker.Unlock()

    if initialized == 0 {
        instance = &Singleton{}
        atomic.StoreUint32(&initialized, 1)//原子装载
    }

    return instance
}

优雅实现

sync.Once提供的Do(f func())方法会使用添加锁来进行原子操作来保证回调函数只执行一次,sync.Once内部本质上也是双重检查的方式。

type Singleton struct{}

var (
    singleton *Singleton
    once      sync.Once
)

func GetInstance() *Singleton {
    once.Do(func() {
        singleton = &Singleton{}
    })
    return singleton
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:JunChow520

查看原文:Go Singleton

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

487 次点击  
加入收藏 微博
下一篇:Go Event
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传