天天看点

python多线程中只初始化一次的单例模式

python中的单例可以利用__new__和__init__来实现。每次创建实例的时候总会获得同一个实例,但是每次也会执行__init__方法。这就会造成单例中的属性会被修改,更重要的是实例会被重新初始化。有时候我们并不希望再次初始化实例,我们希望直接获得已经创建好的实例。应用类变量和锁机制,可以实现需求。

一个简单的单例

import threading
from concurrent.futures.thread import ThreadPoolExecutor


class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):

        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, num):

        self.num = num
        self.__init_flag = True

    def get_name(self):
        return self.num


def run_job(num):
    obj = Singleton(num)
    print("Thread:%s ID:%s NUM:%s" % (num, id(obj), obj.num))


with ThreadPoolExecutor(max_workers=5) as pool:
    for line in range(10):
        pool.submit(run_job, line)

           

上面样例的执行结果为。

Thread:0 ID:43341256 NUM:0

Thread:1 ID:43341256 NUM:1

Thread:2 ID:43341256 NUM:2

Thread:3 ID:43341256 NUM:3

Thread:4 ID:43341256 NUM:4

Thread:5 ID:43341256 NUM:5Thread:6 ID:43341256 NUM:6

Thread:7 ID:43341256 NUM:7

Thread:8 ID:43341256 NUM:8

Thread:9 ID:43341256 NUM:9

从结果中看到执行的10个线程中所创建的实例是同一个,因为实例的 id 是相同的,所以实例对象所指的内存地址是同一个,是同一个实例。但是它们的属性 num 是不同的,这是因为虽然实例是同一个,内存地址也是一个,但是每个实例都初始化了一遍(执行了一遍 __init__ 方法),初始化的时候对实例的属性 num 值进行了覆盖。

它们确确实实是同一个实例,但是属性值却发生了改变。这就像是“一个人”作为一个实例,而这个人改了名字,本来叫“小小”,后来改名叫“大大”,不管叫啥他都是同一个人。

只初始化一次的单例模式

有些时候我们创建一个单例后并不想让它修改属性。我们想第一次创建实例完成后,第二次创建的时候直接返回单例,不要在去执行初始化方法,避免多次初始化带来的资源和时间开销。样例代码如下所示。

import threading
from concurrent.futures.thread import ThreadPoolExecutor


class Singleton:
    _lock_1 = threading.Lock()
    _lock_2 = threading.Lock()
    _instance = None
    __init_flag = False

    def __new__(cls, *args, **kwargs):
        if cls._instance:
            return cls._instance
        with cls._lock_1:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance

    def __init__(self, num):

        if self.__init_flag:
            return

        with self._lock_2:
            if self.__init_flag:
                return

            self.num = num
            self.__init_flag = True

    def get_name(self):
        return self.num


def run_job(num):
    obj = Singleton(num)
    print("Thread:%s ID:%s NUM:%s" % (num, id(obj), obj.num))


with ThreadPoolExecutor(max_workers=10) as pool:
    for line in range(50):
        pool.submit(run_job, line)

# task = []
# for i in range(10):
#     t = threading.Thread(target=run_job, args=(i,))
#     task.append(t)
#
# for one in task:
#     one.start()
#
# for one in task:
#     one.join()
           

执行结果为。

Thread:0 ID:43669064 NUM:0

Thread:1 ID:43669064 NUM:0

Thread:2 ID:43669064 NUM:0

Thread:3 ID:43669064 NUM:0

Thread:4 ID:43669064 NUM:0

Thread:5 ID:43669064 NUM:0

Thread:6 ID:43669064 NUM:0

Thread:7 ID:43669064 NUM:0Thread:8 ID:43669064 NUM:0

Thread:9 ID:43669064 NUM:0