Go 常见设计模式之单例模式详解

 更新时间:2023年07月06日 10:23:26   作者:江湖十年  
单例模式是设计模式中最简单的一种模式,单例模式能够确保无论对象被实例化多少次,全局都只有一个实例存在,在Go 语言有多种方式可以实现单例模式,所以我们今天就来一起学习下吧

饿汉式

饿汉式实现单例模式非常简单,直接看代码:

package singleton
type singleton struct{}
var instance = &singleton{}
func GetSingleton() *singleton {
	return instance
}

singleton 包在被导入时会自动初始化 instance 实例,使用时通过调用 singleton.GetSingleton() 函数即可获得 singleton 这个结构体的单例对象。

由于单例对象是在包加载时立即被创建出来,所以也就有了这个形象的名称叫作饿汉式。与之对应的另一种实现方式叫作懒汉式,当实例被第一次使用时才会被创建。

需要注意的是,尽管饿汉式实现单例模式如此简单,但大多数情况下仍不被推荐使用,因为如果单例实例化时初始化内容过多,可能造成程序加载用时较长。

懒汉式

接下来我们再来看下如何通过懒汉式实现单例模式:

package singleton
type singleton struct{}
var instance *singleton
func GetSingleton() *singleton {
	if instance == nil {
		instance = &singleton{}
	}
	return instance
}

相较于饿汉式的实现,我们把实例化 singleton 结构体部分的代码移到了 GetSingleton() 函数内部。这样一来,就将对象实例化的步骤延迟到了 GetSingleton() 被第一次调用时。

通过 instance == nil 的判断来实现单例并不十分可靠,当有多个 goroutine 同时调用 GetSingleton() 时无法保证并发安全。

支持并发的单例

如果你用 Go 语言写过并发编程,那么应该可以很快想到解决懒汉式单例模式并发安全问题的方案:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
	mu.Lock()
	defer mu.Unlock()
	if instance == nil {
		instance = &singleton{}
	}
	return instance
}

我们对代码的主要修改就是在 GetSingleton() 函数最开始加了如下两行代码:

mu.Lock()
defer mu.Unlock()

通过加锁的机制,就可以保证这个实现单例模式的函数是并发安全的。

不过这样也有些问题,因为用了锁机制,每次调用 GetSingleton() 时程序都会进行加锁、解锁的步骤,这样会导致程序性能的下降。

双重锁定

加锁导致程序性能下降,但我们又不得不用锁来保证程序的并发安全,于是有人想出了双重锁定(Double-Check Locking)的方案:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
	if instance == nil {
		mu.Lock()
		defer mu.Unlock()
		if instance == nil {
			instance = &singleton{}
		}
	}
	return instance
}

可以看到,所谓的双重锁定实际上就是在程序加锁前又加了一层 instance == nil 判断,这样就兼顾了性能和安全两个方面。

不过这段代码看起来有些奇怪,既然外层已经判断了 instance == nil,加锁后却又进行了第二次 instance == nil 判断。其实外层的 instance == nil 判断是为了提高程序的执行效率,因为如果 instance 已经存在,则无需进入 if 逻辑,程序直接返回 instance 即可。这样就免去了原来每次调用 GetSingleton() 都上锁的操作,将加锁的粒度更加精细化。而内层的 instance == nil 判断则是考虑了并发安全,在极端情况下,多个 goroutine 同时走到了加锁这一步,内层判断就起到作用了。

Gopher 惯用方案

虽然我们通过双重锁定机制兼顾和性能和并发安全,但代码有些丑陋,不符合广大 Gopher 的期待。好在 Go 语言在 sync 包中提供了 Once 机制能够帮助我们写出更加优雅的代码:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetSingleton() *singleton {
	once.Do(func() {
		instance = &singleton{}
	})
	return instance
}

Once 是一个结构体,在执行 Do 方法的内部通过 atomic 操作和加锁机制来保证并发安全,且 once.Do 能够保证多个 goroutine 同时执行时 &singleton{} 只被创建一次。

其实 Once 并不神秘,其内部实现跟上面使用的双重锁定机制非常类似,只不过把 instance == nil 换成了 atomic 操作,感兴趣的同学可以查看下其对应源码。

总结

以上就是 Go 语言中实现单例模式的几种常用套路,经过对比可以得出结论,最推荐的方式是使用 once.Do 来实现,sync.Once 包帮我们隐藏了部分细节,却可以让代码可读性得到很大提升。

希望此文能对你有所帮助。

到此这篇关于Go 常见设计模式之单例模式详解的文章就介绍到这了,更多相关Go单例模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • go 微服务框架kratos使用中间件的方法

    go 微服务框架kratos使用中间件的方法

    在go语言中,中间件是一种用于处理http请求的开发模式,允许开发人员在请求到达处理程序之前或之后执行特定的操作,如日志记录、身份验证、错误处理等,这篇文章主要介绍了go 微服务框架kratos使用中间件的方法,需要的朋友可以参考下
    2024-05-05
  • 使用Go语言编写一个简单的Web框架

    使用Go语言编写一个简单的Web框架

    Go语言(又称Golang)因其高效的性能和简洁的语法,在编写Web框架方面表现出色,下面将详细介绍如何使用Go语言编写一个简单的Web框架,文中有详细的代码供大家参考,需要的朋友可以参考下
    2024-05-05
  • go mode tidy出现报错go: warning: “all“ matched no packages的解决方法

    go mode tidy出现报错go: warning: “all“ matched no package

    使用go的时候我们一般都会使用go mode管理,下面这篇文章主要给大家介绍了关于go mode tidy出现报错go: warning: “all“ matched no packages的解决方法,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • 使用Golang实现WebSocket心跳机制

    使用Golang实现WebSocket心跳机制

    WebSocket是一种在客户端和服务器之间实现全双工通信的协议,它允许实时地传输数据,并且比传统的HTTP请求更加高效,在使用Golang构建WebSocket应用程序时,一个重要的考虑因素是如何实现心跳机制,所以本文将探讨如何使用Golang实现WebSocket心跳
    2023-11-11
  • Go语言zip文件的读写操作

    Go语言zip文件的读写操作

    本文主要介绍了Go语言zip文件的读写操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Go实现后台任务调度系统的实例代码

    Go实现后台任务调度系统的实例代码

    平常我们在开发API的时候,前端传递过来的大批数据需要经过后端处理,如果后端处理的速度快,前端响应就快,反之则很慢,影响用户体验,为了解决这一问题,需要我们自己实现后台任务调度系统,本文将介绍如何用Go语言实现后台任务调度系统,需要的朋友可以参考下
    2023-06-06
  • 4个场景教会你Go中Goroutine和通道是怎么用的

    4个场景教会你Go中Goroutine和通道是怎么用的

    本篇给出了4个在运维开发工作中较为常见的且也是比较典型的场景,通过这些场景来带大家掌握Go中Goroutine和通道是怎么使用的,需要的可以学习一下
    2023-05-05
  • vscode搭建go开发环境案例详解

    vscode搭建go开发环境案例详解

    对于Visual Studio Code开发工具,有一款优秀的GoLang插件,今天通过本文给大家介绍下vscode搭建go开发环境的详细教程,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • GO语言延迟函数defer用法详解

    GO语言延迟函数defer用法详解

    本文主要介绍了GO语言延迟函数defer用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • Golang动态数组的实现示例

    Golang动态数组的实现示例

    动态数组能自动调整大小,与静态数组不同,其大小不固定,可根据需求变化,实现通常依赖于数据结构如链表或数组加额外信息,本文就来介绍一下Golang动态数组的实现示例,感兴趣的可以了解一下
    2024-10-10

最新评论