Go 语言实现创建型设计模式 - 单例模式

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Go 语言实现创建型设计模式 - 单例模式

介绍

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。

因为它同时解决了两个问题,所以它违反了单一职责原则。

使用场景

什么场景适合使用单例模式呢?

某个类对于所有客户端只有一个可用的实例

比如记录应用程序的运行日志,因为记录日志的文件只有一个,所以只能有一个日志类的实例向日志文件中写入,否则会出现日志内容互相覆盖的问题。

需要更加严格地控制全局变量

所谓更加严格地控制全局变量,即使用单例模式确保一个类只有一个实例,除了该类自己以外,无法通过任何方式替换缓存的实例(控制全局变量)。

实现方式

在 Go 语言中,没有类 Class 的概念,我们可以使用结构体 struct 替代。

  1. 定义一个私有变量,用于保存单例类的实例。
  2. 定义一个公有函数,用于获取单例类的实例。
  3. 在公有函数中实现 “延迟实例化”。

04

Go 实现

实现单例模式,一般分为三种方式,分别是急切实例化(饿汉式)、延迟实例化(懒汉式)和双重检查加锁实例化。

此外,Go 标准库 sync/once,也可用于实现单例模式。

急切实例化:

急切实例化(饿汉式)是指在导入包时自动创建实例,并且创建的实例会一直存储在内存中,它是并发安全的,可以使用 init() 初始化函数实现。

一般用于实例占用资源少,并且使用频率高的场景。

type singletonV1 struct {
}
var instance *singletonV1
func init() {
 instance = new(singletonV1)
 fmt.Printf("%p\n", instance)
}
func GetInstance() *singletonV1 {
 return instance
}

延迟实例化:

延迟实例化(懒汉式)是指在导入包时不自动创建实例,而是在初次使用时,才会创建实例。它不是并发安全的,可以通过加锁确保协程的并发安全,但是会影响程序的性能。

一般用于实例占用资源多,并且使用率低的场景。

非并发安全:

type singletonV2 struct {
}
var instance *singletonV2
func GetInstance() *singletonV2 {
 if instance == nil {
  instance = new(singletonV2)
 }
 fmt.Printf("%p\n", instance)
 return instance
}

并发安全:

type singletonV3 struct {
}
var instance *singletonV3
var mu sync.Mutex
func GetInstance() *singletonV3 {
 if instance == nil {
  mu.Lock()
  defer mu.Unlock()
  instance = new(singletonV3)
 }
 fmt.Printf("%p\n", instance)
 return instance
}

双重检查加锁实例化:

双重检查加锁实例化实际上是对通过锁支持并发的延迟实例化的优化,减少锁操作,降低性能损耗。

type singletonV4 struct {
}
var instance *singletonV4
var mu sync.Mutex
func GetInstance() *singletonV4 {
 if instance == nil {
  mu.Lock()
  defer mu.Unlock()
  if instance == nil {
   instance = new(singletonV4)
  }
 }
 fmt.Printf("%p\n", instance)
 return instance
}

阅读上面这段代码,第一次 nil 判断,是为了减少锁操作,第二次 nil 判断,是为了确保只有一个争抢到锁的协程创建实例。

双重检查加锁实例化(原子操作):

双重检查加锁实例化的两次检查,我们还可以将第一次 nil 判断,改为通过使用 sync/atomic 包的原子操作判断,决定是否需要进行争抢锁。

type singletonV5 struct {
}
var instance *singletonV5
var mu sync.Mutex
var done uint32
func GetInstance() *singletonV5 {
 if atomic.LoadUint32(&done) == 0 {
  mu.Lock()
  defer mu.Unlock()
  if instance == nil {
   defer atomic.StoreUint32(&done, 1)
   instance = new(singletonV5)
  }
 }
 fmt.Printf("%p\n", instance)
 return instance
}

sync/once:

我们在介绍 Go 语言并发的文章中,了解到 sync/once 包的 Do() 方法可以确保只执行一次。

type singletonV6 struct {
}
var instance *singletonV6
var once sync.Once
func GetInstance() *singletonV6 {
 once.Do(func() {
  instance = new(singletonV6)
 })
 fmt.Printf("%p\n", instance)
 return instance
}

我们可以通过阅读 sync/once 包的源码发现,实际上 Do() 方法也是使用了 sync/atomic 包的 StoreUint32 方法和 LoadUint32() 方法。

05

总结

本文我们介绍了创建型设计模式-单例模式,并且介绍了几种 Go 实现方式。

需要注意的是,我们在高并发场景中,需要考虑并发安全的问题。

推荐阅读:

参考资料:

  1. https://en.wikipedia.org/wiki/Software_design_pattern
  2. https://en.wikipedia.org/wiki/Design_Patterns
相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
20天前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
73 1
|
2月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
258 1
|
2月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
302 0
|
2月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
205 0
|
2月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
171 0
|
2月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
267 0
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
319 0

热门文章

最新文章