建议50:单例模式---多线程环境

修改__new__:

class S(object):
    lock = threading.Lock()
    def __new__(cls, *args, **kw):
        if hasattr(cls, '_instance'):
            print '1'
            return cls._instance
        cls.lock.acquire()
        try:
            cls._instance =object.__new__(cls, *args, **kw)
        finally:
            cls.lock.release()
        print '3'
        return cls._instance
    def p(self):
        print 'called p'
for i in range(5):
    s=S()
    print id(s)
    threading.Thread(target=s.p).start()

 

3
139857531623824
called p
 1
139857531623824
called p
 1
139857531623824
1
 called p
139857531623824
1
 called p
139857531623824
called p

 

装饰器:

from functools import wraps
def singleton(cls):
    @wraps(cls)
    def getinstance(*args, **kw):
        if not hasattr(cls, '_instance'):
            cls._instance = cls(*args, **kw)
        return cls._instance
    return getinstance

@singleton
class MyClass(object):
    def p(self):
        print 'p called\n'

import threading
for i in range(5):
    m=MyClass()
    print id(m)
    threading.Thread(target=m.p).start()
139648094936592
p called

139648094936592
p called

139648094936592
p called
139648094936592

p called
139648094936592

p called

 

可以使用hasattr(cls, '_instance')判断,cls是否有_instance属性,决定是否构造实例。

也可以定义类变量_instance,判断cls的类变量_instance是否为None,决定是否构造实例。

也可以定义类变量objs为字典,判断cls的类变量objs是否有_instance的key,决定是否构造实例。

import threading
class Singleton(object):
    _instance = None
    lock = threading.Lock()
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            print '1'
            cls.lock.acquire()
            try:
                cls._instance =object.__new__(cls, *args, **kw)
            finally:
                cls.lock.release()
        print '3'
        return cls._instance
    def p(self):
        print 'called p'

for i in range(5):
    s=Singleton()
    print id(s)
    threading.Thread(target=s.p).start()
import threading
class Singleton(object):
    objs = {}
    lock = threading.Lock()
    def __new__(cls, *args, **kw):
        if cls not in cls.objs:
            print '1'
            cls.lock.acquire()
            try:
                cls.objs[cls] = super(Singleton, cls).__new__(cls, *args, **kw)
            finally:
                cls.lock.release()
        print '3'
        return cls.objs[cls]
    def p(self):
        print 'called p'

for i in range(5):
    s=Singleton()
    print id(s)
    threading.Thread(target=s.p).start()

 

1
139630141873296
called p3
3
139630141873296 called p3 139630141873296 called p 3 139630141873296 called p 3 139630141873296 called p

 

修改元类:

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instances'):
            cls._instances = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances

# Python2
class MyClass(object):
    __metaclass__ = Singleton
    def p(self):
        print 'p called'

# Python3
# class MyClass(metaclass=Singleton):
#     def p(self):
#         print 'p called'

for i in range(5):
    s=MyClass()
    print id(s)
    threading.Thread(target=s.p).start()

 

139924625706512
p called

139924625706512
p called

139924625706512
p called

139924625706512
p called

139924625706512
p called

 

 

 

 

 

 

 

 

最优雅的单例模式,多线程同样适用:

在Python的官方网站:给了两个例子是用装饰符来修饰类,从而使得类变成了单例模式,使得我们可以通过更加简单的方式去实现单例模式
例子:(这里只给出一个例子,因为更简单,另外一个大家可以看官网Singleton

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance

#
# Sample use
#

@singleton
class Highlander:
    x = 100
    # Of course you can have any attributes or methods you like.

print Highlander() is Highlander() is Highlander #=> True
id(Highlander()) == id(Highlander) #=> True
Highlander().x == Highlander.x == 100 #=> True
Highlander.x = 50
Highlander().x == Highlander.x == 50 #=> True

 

这里简单解释下:

  1. 在定义class Highlander的时候已经执行完所有singleton装饰器中的代码,得到了一个instance,所以这之后所有对Highlander的调用实际上是在调用instance的_call_ 方法。
  2. 我们通过lambda函数定义了_call_方法让它始终返回instance,因此Highlander()和Highlander都返回instance
  3. 同时由于在类定义代码执行时就已经创建了instance,所以后续不论是多线程还是单线程,在调用Highlander时都是在调用instance的_call_方法,也就无需同步了。
  4. 最后我想说的是这种方法简直碉堡了~~~

附上我用于多线程的测试代码:

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance

@singleton
class MyClass(object):
    def p(self):
        print '\np called\n'

import threading
for i in range(5):
    s = MyClass
    print id(s)
    threading.Thread(target=s.p).start()

 

import threading

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance


@singleton
class Highlander:
    x = 100
    # Of course you can have any attributes or methods you like.


def worker():
    hl = Highlander()
    hl.x += 1
    print hl
    print hl.x


def main():
    threads = []
    for _ in xrange(50):
        t = threading.Thread(target=worker)
        threads.append(t)

    for t in threads:
        t.start()

    for t in threads:
        t.join()


if __name__ == '__main__':
    main()

 

 

142
<__main__.Highlander instance at 0x7fafd0d51248><__main__.Highlander instance at 0x7fafd0d51248>

 145
<__main__.Highlander instance at 0x7fafd0d51248>
145
<__main__.Highlander instance at 0x7fafd0d51248>
146
<__main__.Highlander instance at 0x7fafd0d51248>147

 

 

这里的代码有一点小问题,就是在打印的时候有可能x属性已经被别的线程+1了,所以有可能导致同一个数打印

 

posted on 2018-02-12 15:57  myworldworld  阅读(143)  评论(0)    收藏  举报

导航