百度网盘一面:手写一个单例模式

hello,大家好,我是千羽
在百度网盘的首轮面试中,我清晰地记得,面试的最后编程题,采用手写方式成功实现了单例模式。今天再来整理回顾一下单例的模式。
在 Go 语言中,实现单例模式时,饿汉式和懒汉式(常用)模式可以根据实例的初始化时机来区分,尤其在懒汉式模式中需要处理并发安全问题。
使用 sync.Once 是解决并发问题的最佳方式,因为它确保实例的初始化只会执行一次,避免了多线程下的重复创建问题。

代码放置Github:https://github.com/nateshao/gopher-design-pattern

  1. 饿汉式单例模式
    饿汉式模式的特点是实例在程序启动时就初始化,因此它天然是线程安全的。
    代码实现:
    singleton_hungry.go
    package singleton

/**
饿汉式单例模式
饿汉式模式的特点是实例在程序启动时就初始化,因此它天然是线程安全的。
*/
// 单例对象的定义
type singleton struct {
}

// 在程序启动的时候,单例实例就会被创建
var instance = &singleton{}

// 获取单例对象的函数
func GetHungryInstance() *singleton {
return instance
}

说明:
instance 变量在程序启动时已经初始化,保证只会有一个实例。
饿汉式模式在单例类较为简单且实例初始化开销不大的场景中适用。
1.2 饿汉式单元测试
package singleton_test

import (
“github.com/stretchr/testify/assert”
singleton “gopher-design-pattern/01_singleton”
“testing”
)
/**
验证懒汉式单例模式 和 饿汉式单例模式是否返回同一个实例对象

  1. 懒汉式单例,第一次调用时才创建实例。
  2. 饿汉式单例,在程序启动时立即创建实例。
    */
    func TestGetInstance(t *testing.T) {
    assert.True(t, singleton.GetLazyInstance() == singleton.GetHungryInstance())
    }

/**
测试在并发环境下,懒汉式和饿汉式单例是否能够安全工作,并且返回同一个实例。这里使用了 b.RunParallel 来模拟多线程/多协程并发场景

  1. pb.Next():每次循环都会调用,模拟高频率的并发操作。
  2. singleton.GetLazyInstance() != singleton.GetHungryInstance():每次迭代时检查两个单例实例是否不同,
    如果不同则报告错误 (b.Errorf(“test fail”)),意味着在并发环境下,某些情况可能导致单例模式失效。
    */
    func BenchmarkGetInstanceParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
    for pb.Next() {
    if singleton.GetLazyInstance() != singleton.GetHungryInstance() {
    b.Errorf(“test fail”)
    }
    }
    })
    }

1.3 懒汉式单例模式(并发安全)
懒汉式单例模式会在第一次访问时才初始化实例,在并发场景下,如果多个协程同时访问 GetInstance(),可能导致实例化多次。为了确保只创建一个实例,我们使用 sync.Once 来保证线程安全。
实现代码(使用 sync.Once 保证并发安全):
singleton_lazy.go
package singleton

import “sync”

/**
懒汉式单例模式会在第一次访问时才初始化实例,
在并发场景下,如果多个协程同时访问 GetInstance(),可能导致实例化多次。为了确保只创建一个实例,我们使用 sync.Once 来保证线程安全。
*/

// 单例对象的定义
// 使用sync.Once 确保只创建一次实例
var (
lazySinleton *singleton
once = &sync.Once{}
)

// GetLazyInstance 懒汉式
func GetLazyInstance() *singleton {
// once 内的方法只会执行一次,所以不需要再次判断
once.Do(func() {
lazySinleton = &singleton{}
})
return lazySinleton
}

说明:
sync.Once 确保 Do() 内的代码块只会执行一次,即使在多个协程并发访问时也是如此。
在这种实现中,即使有多个协程同时调用 GetInstance(),也不会导致多次实例化,因为 sync.Once 内部使用了互斥锁来保证线程安全。
1.4 懒汉式单元测试
singleton_lazy_test.go
package singleton_test

import (
“github.com/stretchr/testify/assert”
singleton “gopher-design-pattern/01_singleton”
“testing”
)

func TestGetLazyInstance(t *testing.T) {
assert.Equal(t, singleton.GetLazyInstance(), singleton.GetLazyInstance())
}

func BenchmarkGetLazyInstanceParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if singleton.GetLazyInstance() != singleton.GetLazyInstance() {
b.Errorf(“test fail”)
}
}
})
}

1.5 总结:
饿汉式:实例在程序启动时创建,天然线程安全,但不适合需要延迟加载的场景。
懒汉式(使用 sync.Once):实例在第一次使用时创建,使用 sync.Once 确保并发安全,推荐这种方式来实现线程安全的懒汉式单例模式。

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/421274.html

(0)
联系我们
联系我们
分享本页
返回顶部