你真的会用go语言写单例模式吗?

  最近在学习Golang,想着能够就之前的知识作一些串通,加上了解到go语言也是面向对象编程语言以后。在最近的开发过程当中,我碰到一个问题,要用go语言实现单例模式。本着“天下知识,同根同源”(我瞎掰的~),我心想,这有什么难的,但是真正作起来,仍是碰到了很多问题。编程

  下面是个人经历:安全

  1.我先是完成了个人初版单例模式,就是非并发,最简单的一种,懒汉模式:并发

var instance *single
type single struct{
	Name string
}
func GetInstance()*single{
	if m == nil{
		m = &single{}
	}
	return m
}
func main(){
	a := GetInstance()
	a.Name = "a"
	b := GetInstance()
	b.Name = "b"
	fmt.Println(&a.Name, a)
	fmt.Println(&b.Name, b)
	fmt.Printf("%p %T\n", a, a)
	fmt.Printf("%p %T\n", b, b)
}

  结果以下:编程语言

0xc04203e1b0 &{b}
0xc04203e1b0 &{b}
0xc04203e1b0 *main.single
0xc04203e1b0 *main.single函数

  能够看到,咱们已经实现了简单的单例模式,咱们申请了两次实例,在改变一个第二个实例的字段以后,第一个也随之改变了。并且从他们的地址都相同也能够看出是同一个对象。可是,这样简陋的单例模式在并发下就容易出错,非线程安全的。高并发

  如今咱们是在并发的状况下去调用的 GetInstance函数,如今刚好第一个goroutine执行到m = &Manager {}这句话以前,第二个goroutine也来获取实例了,第二个goroutine去判断m是否是nil,由于m = &Manager{}尚未来得及执行,因此m确定是nil,如今出现的问题就是if中的语句可能会执行两遍!学习

  2.紧接着咱们作了一些改进,给单例模式加了锁:优化

var m *single
var lock sync.Mutex
type single struct{
	Name string
}
func GetInstance()*single{
	lock.Lock()
	defer lock.Unlock()
	if m == nil{
		m = &single{}
	}
	return m
}

  结果同上。线程

  与此同时,新的问题出现了,在高并发环境下,如今无论什么状况下都会上一把锁,并且加锁的代价是很大的,有没有办法继续对咱们的代码进行进一步的优化呢?code

  3.双重锁机制:

var m *single
var lock sync.Mutex
type single struct{
	Name string
}
func GetInstance()*single{
	if m == nil{
		lock.Lock()
		defer lock.Unlock()
		if m == nil{
			m = &single{}
		}
	}
	return m
}

  此次咱们用了两个判断,并且咱们将同步锁放在了条件判断以后,这样作就避免了每次调用都加锁,提升了代码的执行效率。理论上写到这里已是很完美的单例模式了,可是咱们在go语言里,咱们有一个很优雅的写法。

  4.sync包里的Once.Do()方法

var m *single
var once sync.Once

type single struct{
	Name string
}
func GetInstance()*single{
	once.Do(func() {
		m = &single{}
	})
	return m
}

  Once.Do方法的参数是一个函数,这里咱们给的是一个匿名函数,在这个函数中咱们作的工做很简单,就是去赋值m变量,并且go能保证这个函数中的代码仅仅执行一次!

 

  之后在用go语言写单例模式的时候,可不要再傻傻的去使用前面那些例子了,既然已经有了优雅又强大的方法,咱们直接用就完了。

相关文章
相关标签/搜索