golang坑集-单例模式
Posted go野生程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang坑集-单例模式相关的知识,希望对你有一定的参考价值。
单例模式可以说是设计模式当中最为简单的一种,在开发中,为了避免那些需要反复调用的实例被多次初始化,将实例设计为单例之后就可以实现一次初始化,多处调用的好处。通常用得最多的地方是连接数据库。
首先,看一下php当中实现单例模式的方法:
声明一个静态属性,当getInstance方法被调用之后,将类的实例赋值给$instance,利用静态属性可以保存上一次状态的特性达到了我们需要的一次初始化,多次调用的效果。
测试,反复调用demo::getInstance(),页面只输出了一次“初始化”。
其实,在PHP里面利用单例模式的场景并不多,由于PHP的应用场景几乎都集中在网页开发领域,对于通常只有1到2秒请求时间的http来说,单例模式并没有发挥很大的作用。
我个人觉得,在golang当中单例模式能发挥更大的作用。由于go能够直接代替nginx创建一个服务,它的实现方式可以理解为一直在用死循环去进行端口监听,无论是否有请求,主程序都不会退出。因此,在它进行端口监听的时候去实现单例模式,这样可以做到真正的一次初始化的目的了。
废话不多讲,依样画葫芦,利用golang来实现单例模式:
在入口函数中调用了两次用于初始化的函数GetInstace(),
从结果中看出,它输出预期中的结果。看样子它可以好好工作了,但其实这里面有一个很优雅的坑。
php实现单例之所以如此简单而不需过多考虑后果主要的原因在于它的每一处逻辑都掌握在开发者手中,因为它是同步阻塞的。 在手头上的事情做完之前它是不会去做另一件事情的。对于天生支持并发的golang来说,很多事情可以同步去做,也就是异步非阻塞。这就导致了,在并发的逻辑中,有N处代码运行到了if Pattern == nil,在理论上,它们将同时对Pattern进行赋值。来一段代码验证这个问题,在函数之前加入关键字go就实现代码的并行运行,不得不佩服,优雅是go的设计哲学之一:
结果编译不通过。
很显然,在go GetInstance()执行完成之前,代码已经执行到了Pattern.Func1(),而此时,Pattern还是一个nil值。
在这里,调用多个go关键字让初始化函数并行运行,并利用1秒的时间差来模拟其他代码逻辑,使它阻塞Pattern.Func1()以便实现刚刚的猜想:
输出的结果是未知的,Pattern被赋值了一次或多次,这种情况是随机的,不可控的。这与我们所想象的单例模式相去甚远:
所以,为了在并发情况下继续保持单例必须引入锁的机制,golang标准库sync提供了完善的互斥锁。
声明变量新的变量指向一个互斥锁,在sync包中主要有两种锁,结合应用场景我们使用RWMutex类型(单写多读),在所有的线程当中它将独占一个锁的时间片,阻塞其他线程:
输出:
从上图的输出结果可见,代码逻辑已经变为同步阻塞了,在执行第一go GetInstance()时,这一线程阻塞了其他线程,对Pattern赋值成功之后锁才被释放。其他的线程得以复用Pattern的值。
然而,加锁的代价是相当大,上面的代码表示每一次执行go GetInstance()都会对代码进行阻塞,单例模式所奉行的资源复用、资源节约在这里又变得相去甚远。当然,还有别的办法:
sync当中的Once类已经在底层实现了使代码一次调用的逻辑。它接收一个函数,在每次执行这一函数时底层都会进行判断,这样我们就能利用这一特性实现适用于并发情况下的单例模式了。
以上是关于golang坑集-单例模式的主要内容,如果未能解决你的问题,请参考以下文章