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
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
30天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 2
|
11天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
19天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
14 1
|
25天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
24 0
|
28天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
本教程详细讲解了Kotlin中的单例模式实现,包括饿汉式、懒汉式、双重检查锁、静态内部类及枚举类等方法,适合需要深入了解Kotlin单例模式的开发者。快速学习者可参考“简洁”系列教程。
27 0
|
28天前
|
设计模式 存储 数据库连接
Python编程中的设计模式之美:单例模式的妙用与实现###
本文将深入浅出地探讨Python编程中的一种重要设计模式——单例模式。通过生动的比喻、清晰的逻辑和实用的代码示例,让读者轻松理解单例模式的核心概念、应用场景及如何在Python中高效实现。无论是初学者还是有经验的开发者,都能从中获得启发,提升对设计模式的理解和应用能力。 ###
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
16天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
18天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    43
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    54
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    62
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    57
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    41
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    106
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78