当前位置: 首页 > 科技观察

简单的单例模式,Go版本的实现你写对了吗?

时间:2023-03-19 23:54:49 科技观察

大家好,我是站长。首先,我想问你一个问题。在你面试的时候,面试官有没有问过你:“你用过哪些设计模式?”,我猜大多数人的回答都会是singleExample模式,首先。我:“呃……我用过单例、工厂、观察者、反向代理、装饰器、哨兵”……”,面试官内心OS:“我从来没用过这么多……什么是反向代理Ghost,这孩子已经记住了。如果你不介意,我们就下山从头问起。”面试官:“我用过很多,那你能不能告诉我你在什么情况下使用单例,顺便在一张纸上实现单例。”我:“当你需要确保一个类型只有一个实例,需要用到单例模式。”面试官:“好吧,那你纸上实现一下”十分钟后我:“不好意思,我们之前的项目已经封装好了,我只用了它,我没有机会去实现它,所以……”面试官内心OS:“嗯,这个面试KPI要求30分钟,不够。只剩二十分钟了,再问一遍就好,让他回去等信。《面试兵……上面是我给大家补的一个场景,有雷同之处请忍住,别笑站内喷~。单例模式虽然简单,但还是有一些东西的说起来。首先,它被广泛使用。如果不注意,在多线程环境下很容易导致bug。今天简单说一下单例模式的应用,以及如何正确实现Go语言中的单例模式,上面对话中的单例模式是正确的,单例模式是用来控制类型实例的个数的,当需要保证一个类型只有一个实例时,需要使用一个单例模式,因为需要控制数量,可以想象只能关闭对实例的访问,不能再新建一个,所以单例模式也会提供一个全局端口来访问实例,一般命名为GetInstance等函数作为p例如访问权。并且因为创建实例的时间,单例模式可以拆分为饿汉模式和懒汉模式。前者适用于创建一个在程序初始化时就已经确定要加载的早期类型实例,比如项目的数据库实例。后者其实是一种延迟加载模式,适用于在程序执行过程中满足条件时创建加载的类型实例。下面我们用Go代码来实现这两种单例模式。饿汉模式Go语言实现该模式时,使用Go的init函数非常方便。如果你想全面了解Goinit函数,可以看之前的老文你必须记住的Go语言init函数的六大特点。下面使用单例模式。返回数据库连接实例,相信大家在项目中也看到过类似的代码。packagedao//饿汉式单例//注意非导出类型的定义databaseConnstruct{...}vardbConn*databaseConnfuncinit(){dbConn=&databaseConn{}}//GetInstance获取实例funcDb()*databaseConn{returndbConn}在这里,我们不会在初始化数据库的细节上浪费太多时间。实际情况一定是从配置中心加载数据库连接配置,然后实例化数据库连接对象。看到这里可能有人会有疑问,你这个程序过程中只有一个数据连接实例,那么那么多请求可以用一个数据库连接吗?咦,这是对数据库连接的一个抽象,这个实例会维护一个连接池,这里才是请求数据库的真正连接。你是不是有点晕,看你项目这块里的代码有点晕。通常,当你初始化一个实例时,你将被允许设置连接池配置,如最大连接数、空闲连接数和生存时间。Lazymode懒惰模式——通俗地说就是懒加载,但是要特别注意这块地方。在并发环境下,判断实例是否已经创建时,是否使用当前读取。在一些讲授设计模式的教程中,一般都会在这种情况下给出一个例子——使用Java双锁实现线程安全的单例模式。双锁指的是volatile和synchronized。classSingleton{privatevolatilestaticSingletoninstance=null;privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null)instance=newSingleton();}}返回实例;}}在上面的例子中,如果没有给实例属性加上volatile修饰符,那么虽然创建过程已经用synchronized锁定了类,但是有可能读取实例的线程缓存滞后。有可能此时属性已经被其他线程初始化,所以必须加上volatile保证当前读取(读取主存中属性的状态)。那么Go中没有volatile这种机制,怎么办呢?聪明的你一定能猜到,我们定义了一个实例的状态变量,然后使用原子操作atomic.Load和atomic.Store来读写这个状态变量。这不就是了悟吗?喜欢下面的:如果你对Go的原子操作不熟悉,请阅读旧文Golang的五种原子操作详解用法*singleton{ifatomic.LoadUInt32(&initialized)==1{//原子操作返回实例}mu.Lock()defermu.Unlock()ifinitialized==0{instance=&singleton{}atomic.StoreUint32(&initialized,1)}returninstance}确实相当于把上面的Java例子翻译成Go,但是还有一种更Gnative的写法,比这种写法更简洁。如果我们使用Go比较地道的写法,我们可以在其sync库中使用并发同步原语Once来实现:{once.Do(func(){instance=&singleton{}})returninstance}关于sync.One的使用及其实现原理...发现我的Go并发编程中并没有写Once的原理series语言,可能是我觉得太简单了,以后再补上。。。不过只是原理分析,具体如何应用也可以参考Go语言中sync包的详细应用。综上所述,这篇文章其实是告诉你单例模式的应用,以及如何在Go中实现单例模式的版本。现在大部分教程都是用Java讲设计模式的。虽然我们可以直接翻译它们,但有些SometimesGo有一些更原生的实现,使得实现更加简洁。