天天看點

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