Go 单例形式讲解和代码示例

聊聊在生产环境中使用Docker的最佳实践 – 掘金 ()

单例是一种创立型规划形式, 让你能够保证一个类只有一个实例, 并提供一个拜访该实例的大局节点。

单例拥有与大局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。

在某些其他上下文中, 你不能使用依赖于单例的类。 你也将有必要使用单例类。 绝大多数情况下, 该限制会在创立单元测试时出现。

概念示例

通常而言, 单例实例会在结构体初次初始化时创立。 为了实现这一操作, 咱们在结构体中界说一个 getInstance获取实例办法。 该办法将担任创立和回来单例实例。 创立后, 每次调用 getInstance时都会回来相同的单例实例。

协程方面又有什么需要留意的吗? 每逢多个协程想要拜访实例时, 单例结构体就有必要回来相同的实例。 正因如此, 单例规划形式的实施作业很简单犯错。 下方的比如表明了创立单例的正确方式。

一些值得留意的当地:

  • 最开始时会有 nil检查, 保证 singleInstance单例实例在最开始时为空。 这是为了防止在每次调用 getInstance办法时都去履行耗费巨大的确认操作。 如果检查不通过, 则就意味着 singleInstance字段已被填充。
  • singleInstance结构体将在确认期间创立。
  • 在获取到锁后还会有另一个 nil检查。 这是为了保证即便是有多个协程绕过了第一次检查, 也只能有一个能够创立单例实例。 否则, 所有协程都会创立自己的单例结构体实例。

single.go: 单例

package main
import (
    "fmt"
    "sync"
)
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("Creating single instance now.")
            singleInstance = &single{}
        } else {
            fmt.Println("Single instance already created.")
        }
    } else {
        fmt.Println("Single instance already created.")
    }
    return singleInstance
}

main.go: 客户端代码

package main
import (
    "fmt"
)
func main() {
    for i := 0; i < 30; i++ {
        go getInstance()
    }
    // Scanln is similar to Scan, but stops scanning at a newline and
    // after the final item there must be a newline or EOF.
    fmt.Scanln()
}

output.txt: 履行成果

Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.

另一个比如

  1. init函数

咱们能够在 init函数中创立单例实例。 这仅适用于实例的早期初始化作业已经确认时。 init函数仅会在包中的每个文件里调用一次, 所以咱们能够确认其只会创立一个实例。

  1. sync.Once

sync.Once仅会履行一次操作。 可查看下面的代码:

syncOnce.go: 单例

package main
import (
    "fmt"
    "sync"
)
var once sync.Once
type single struct {
}
var singleInstance *single
func getInstance() *single {
    if singleInstance == nil {
        once.Do(
            func() {
                fmt.Println("Creating single instance now.")
                singleInstance = &single{}
            })
    } else {
        fmt.Println("Single instance already created.")
    }
    return singleInstance
}

main.go: 客户端代码

package main
import (
    "fmt"
)
func main() {
    for i := 0; i < 30; i++ {
        go getInstance()
    }
    // Scanln is similar to Scan, but stops scanning at a newline and
    // after the final item there must be a newline or EOF.
    fmt.Scanln()
}

output.txt: 履行成果

Creating single instance now.
Single instance already created.
Single instance already created.