Skip to main content

单例模式

code

var lock = &sync.Mutex{}

type single struct {
}

var singleInstance *single

func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creting Single Instance Now")
singleInstance = &single{}
} else {
fmt.Println("Single Instance already created-1")
}
} else {
fmt.Println("Single Instance already created-2")
}
return singleInstance
}

问题

var singleInstance *single 是包级别的全局变量。这意味着它在定义所在的包内是共享的,但对于其他包来说是不可见的,除非通过公开的函数或方法访问。

如果多个包需要访问同一个单例实例,通常会通过提供一个公开的函数(如 GetInstance())来实现。这样,多个包可以通过这个函数获取同一个实例。

这个锁在 if singleInstance == nil { 内部,直接在外部少写一个判断岂不是更好?

将锁移到外部可以简化逻辑,但会降低效率。当前实现使用双重检查锁定,这是为了减少锁的开销。以下是两种方式的对比:

当前实现(双重检查锁定)

  1. 效率:

    • singleInstance 已经被创建的情况下,不需要加锁,直接返回实例。
    • 只有在 singleInstancenil 时才会加锁,减少了不必要的锁操作。
  2. 线程安全:

    • 确保只有一个 goroutine 能创建实例。

移动锁到外部

func getInstance() *single {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating Single Instance Now")
singleInstance = &single{}
} else {
fmt.Println("Single Instance already created")
}
return singleInstance
}
  1. 效率:

    • 每次调用 getInstance 都会加锁,即使 singleInstance 已经存在。
    • 在高并发环境下,可能会导致性能下降。
  2. 线程安全:

    • 同样确保线程安全,但效率较低。

结论

双重检查锁定在性能和线程安全之间提供了良好的平衡。除非有特殊需求,否则当前的实现方式更为高效。

js 没有要加锁

JavaScript 通常不会有这个问题,因为它的执行环境(如浏览器或 Node.js)是单线程的。由于没有多线程竞争访问同一资源的情况,JavaScript 不需要像 Go 这样的锁机制来解决并发问题。

不过,在某些情况下(如 Web Workers 或 Node.js 的多线程模块),JavaScript 也可能需要考虑线程安全。但在大多数情况下,单线程的事件循环模型已经足以避免这些问题。

once.DO

在 Go 中,sync.Once 是一种用于确保某个操作只执行一次的机制。它特别适用于单例模式的实现,因为它能保证初始化代码只会被执行一次,无论有多少个 goroutine 竞争执行。

下面是使用 sync.Once 实现单例模式的示例:

package main

import (
"fmt"
"sync"
)

type single struct{}

var singleInstance *single
var once sync.Once

func getInstance() *single {
once.Do(func() {
fmt.Println("Creating Single Instance Now")
singleInstance = &single{}
})
return singleInstance
}

func main() {
instance1 := getInstance()
instance2 := getInstance()

if instance1 == instance2 {
fmt.Println("Both instances are the same")
}
}

优点

  • 线程安全: sync.Once 本身是线程安全的,确保初始化代码只会执行一次。
  • 简单易用: 不需要手动管理锁,代码更简洁。

使用场景

sync.Once 非常适合用于初始化全局变量或执行某个需要保证只运行一次的操作。