单例模式中不同语言的不同实现-Python教程

资源魔 43 0

明天python视频教程栏目引见单例模式中没有同言语的没有同完成。

媒介

前段工夫正在用 Python 完成营业的时分发现一个坑,精确的来讲是关于 Python 外行人容易踩的坑;

大略代码以下:

class Mom(object):
    name = ''
    sons = []if __name__ == '__main__':
    m1 = Mom()
    m1.name = 'm1'
    m1.sons.append(['s1', 's2'])    print '{} sons={}'.format(m1.name, m1.sons)

    m2 = Mom()
    m2.name = 'm2'
    m2.sons.append(['s3', 's4'])    print '{} sons={}'.format(m2.name, m2.sons)复制代码

起首界说了一个 Mom 的类,它蕴含了一个字符串类型的 name 与列表类型的 sons 属性;

正在应用时起首创立了该类的一个实例 m1 并往 sons 中写入一个列表数据;紧接着又创立了一个实例 m2 ,也往 sons 中写入了另外一个列表数据。

假如是一个 Javaer 很少写 Python 看到这样的代码起首想到的输入应该是:

m1 sons=[['s1', 's2']]
m2 sons=[['s3', 's4']]复制代码

但其实终极的输入后果是:

m1 sons=[['s1', 's2']]
m2 sons=[['s1', 's2'], ['s3', 's4']]复制代码

假如想要达到希冀值需求略微修正一下:

class Mom(object):
    name = ''

    def __init__(self):
        self.sons = []复制代码

只要要修正类的界说就能够了,我置信即便不 Python 相干经历比照这两个代码应该也能猜到缘由:

正在 Python 中假如需求将变量作为实例变量(也就是每一个咱们希冀的输入)时,需求将变量界说到结构函数中,经过 self 拜访。

假如只放正在类中,以及 Java 中的 static 动态变量成果相似;这些数据由类同享,也就能诠释为何会呈现第一种状况,由于此中的 sons 是由 Mom 类同享,以是每一次城市累加。

Python 单例

既然 Python 能够经过类变量达到变量正在同一个类中同享的成果,那能否能够完成单例模式呢?

能够行使 Pythonmetaclass 的特点,静态的管制类的创立。

class Singleton(type):
    _instances = {}    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)        return cls._instances[cls]复制代码

起首创立一个 Singleton 的基类,而后咱们正在咱们需求完成单例的类中将其作为 metaclass

class MySQLDriver:
    __metaclass__ = Singleton    def __init__(self):
        print 'MySQLDriver init.....'复制代码

这样Singleton 就能够管制 MySQLDriver 这个类的创立了;其真实 Singleton 中的 __call__ 能够很容易了解这个单例创立的进程:

  • 界说一个公有的类属性 _instances 的字典(也就是 Java 中的 map)能够做到正在整个类中同享,无论创立几何个实例。
  • 当咱们自界说类应用了 __metaclass__ = Singleton 后,即可以管制自界说类的创立了;假如曾经创立了实例,那就间接从 _instances 掏出工具前往,否则就创立一个实例并写回到 _instances ,有点 Spring 容器的觉得。
if __name__ == '__main__':
    m1 = MySQLDriver()
    m2 = MySQLDriver()
    m3 = MySQLDriver()
    m4 = MySQLDriver()    print m1    print m2    print m3    print m4

MySQLDriver init.....
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>
<__main__.MySQLDriver object at 0x10d848790>复制代码

最初咱们经过试验后果能够看到单例创立胜利。

Go 单例

因为比来团队中有局部营业开端正在用 go ,以是也想看看正在 go 中若何完成单例。

type MySQLDriver struct {
    username string}复制代码

正在这样一个简略的构造体(能够简略了解为 Java 中的 class)中是没法相似于 Python 以及 Java 同样能够申明类同享变量的;go 言语中没有存正在 static 的概念。

但咱们能够正在包中申明一个全局变量来达到一样的成果:

import "fmt"type MySQLDriver struct {
    username string}var mySQLDriver *MySQLDriverfunc GetDriver() *MySQLDriver {    if mySQLDriver == nil {
        mySQLDriver = &MySQLDriver{}
    }    return mySQLDriver
}复制代码

这样正在应用时:

func main() {
    driver := GetDriver()
    driver.username = "cj"
    fmt.Println(driver.username)

    driver2 := GetDriver()
    fmt.Println(driver2.username)

}复制代码

就没有需求间接结构 MySQLDriver ,而是经过GetDriver() 函数来猎取,经过 debug 也能看到 driver 以及 driver1 援用的是同一个内存地点。

这样的完成惯例状况是不甚么成绩的,机智的冤家肯定能想到以及 Java 同样,一旦并发拜访就没那末简略了。

正在 go 中,假如有多个 goroutine 同时拜访GetDriver() ,那大略率会创立多个 MySQLDriver 实例。

这里说的没那末简略实际上是绝对于 Java 来讲的,go 言语中提供了简略的 api 即可完成临界资本的拜访。

var lock sync.Mutexfunc GetDriver() *MySQLDriver {
    lock.Lock()    defer lock.Unlock()    if mySQLDriver == nil {
        fmt.Println("create instance......")
        mySQLDriver = &MySQLDriver{}
    }    return mySQLDriver
}func main() {    for i := 0; i < 100; i++ {        go GetDriver()
    }

    time.Sleep(2000 * time.Millisecond)
}复制代码

略加革新上文的代码,退出了

lock.Lock()defer lock.Unlock()复制代码

代码就能简略的管制临界资本的拜访,即使咱们开启了100个协程并发执行,mySQLDriver 实例也只会被初始化一次。

  • 这里的 defer 相似于 Java 中的 finally ,正在办法挪用前加之 go 要害字便可开启一个协程。

虽然说能餍足并发要求了,但其实这样的完成也不敷优雅;细心想一想这里

mySQLDriver = &MySQLDriver{}复制代码

创立实例只会挪用一次,但后续的每一次挪用都需求加锁从而带来了不用要的开支。

这样的场景每一个言语都是相反的,拿 Java 来讲是否是常常看到这样的单例完成:

public class Singleton {    private Singleton() {}   private volatile static Singleton instance = null;   public static Singleton getInstance() {        if (instance == null) {     
         synchronized (Singleton.class){           if (instance == null) {    
             instance = new Singleton();
               }
            }
         }        return instance;
    }
}复制代码

这是一个典型的两重反省的单例,这里做了两次反省即可以免后续其余线程再次拜访锁。

一样的关于 go 来讲也相似:

func GetDriver() *MySQLDriver {    if mySQLDriver == nil {
        lock.Lock()        defer lock.Unlock()        if mySQLDriver == nil {
            fmt.Println("create instance......")
            mySQLDriver = &MySQLDriver{}
        }
    }    return mySQLDriver
}复制代码

以及 Java 同样,正在原有根底上额定做一次判别也能达到一样的成果。

但有无感觉这样的代码十分繁琐,这一点 go 提供的 api 就十分省事了:

var once sync.Oncefunc GetDriver() *MySQLDriver {
    once.Do(func() {        if mySQLDriver == nil {
            fmt.Println("create instance......")
            mySQLDriver = &MySQLDriver{}
        }
    })    return mySQLDriver
}复制代码

实质上咱们只要要不论正在甚么状况下 MySQLDriver 实例只初始化一次就能达到单例的目的,以是行使 once.Do() 就能让代码只执行一次。

查看源码会发现 once.Do() 也是经过锁来完成,只是正在加锁以前行使底层的原子操作做了一次校验,从而防止每一次都要加锁,功能会更好。

总结

置信各人一样平常开发中很少会碰着需求本人完成一个单例;起首年夜局部状况下咱们都没有需求单例,即便是需求,框架通常也都有集成。

相似于 go 这样框架较少,需求咱们本人完成时其实也没有需求过多思考并发的成绩;摸摸本人肚子左上方的地位想一想,本人写的这个工具真的同时有几百上千的并发来创立嘛?

不外经过这个比照会发现 go 的语法的确要比 Java 简约太多,同时轻量级的协程和简略易用的并发对象支持看起来都要比 Java 优雅许多;后续无机会再接着深化。

相干收费学习保举:python视频教程

以上就是单例模式中没有同言语的没有同完成的具体内容,更多请存眷资源魔其它相干文章!

标签: 单例模式 Python python教程 python编程 python使用问题

抱歉,评论功能暂时关闭!