Python単例モード

6666 ワード

概要
Singleton Patternは、クラスが1つのインスタンスしか存在しないことを保証することを主な目的とする一般的なソフトウェア設計モデルです.システム全体でクラスに1つのインスタンスしか表示されないことを望む場合は、単一のオブジェクトを使用できます.
例えば、あるサーバプログラムの構成情報は1つのファイルに格納され、クライアントはAppConfigのクラスを通じて構成ファイルの情報を読み出す.プログラムの実行中にコンフィギュレーション・ファイルのコンテンツを使用する必要がある場所が多い場合、つまりAppConfigオブジェクトのインスタンスを作成する必要がある場所が多いため、システムに複数のAppConfigのインスタンス・オブジェクトが存在し、特にコンフィギュレーション・ファイルのコンテンツが多い場合、メモリ・リソースが大幅に浪費されます.実際、AppConfigのようなクラスでは、プログラムの実行中にインスタンスオブジェクトが1つしか存在しないことを望んでいます.
モジュールを使用した単一モードの実装
実は、Pythonのモジュールは天然の単例モードです.モジュールは1回目のインポート時に.pycファイルを生成し、2回目のインポート時に.pycファイルを直接ロードし、モジュールコードを再実行しないからです.したがって,関連する関数とデータを1つのモジュールに定義するだけで,単一のオブジェクトを得ることができる.本当に単一のクラスがほしい場合は、次のことを考えてみましょう.
mysingleton.py:
class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

上記のコードをファイルmysingleton.pyに保存し、使用する場合は、このファイルのオブジェクトを他のファイルに直接インポートします.このオブジェクトは、単一のモードのオブジェクトです.
from a import singleton

アクセサリーを使う
def Singleton(cls):
    _instance = {}

    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]

    return _singleton


@Singleton
class A(object):
    a = 1

    def __init__(self, x=0):
        self.x = x


a1 = A(2)
a2 = A(3)

print("a1: ", a1, "a1.x: ", a1.x)
print("a2: ", a2, "a2.x: ", a2.x)

印刷出力:
a1:  <__main__.a object="" at=""> a1.x:  2
a2:  <__main__.a object="" at=""> a2.x:  2

クラスの操作
import threading

class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

def task(arg):
    obj = Singleton.instance()
    print(obj)

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

一般的には、これで一例モードが完了すると考えられていますが、マルチスレッドを使用すると問題が発生し、印刷結果は以下の通りです.
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">

実行速度が速すぎるため、initメソッドにIO操作がいくつかあると、問題が発見されます.次にtime.sleepシミュレーションでは、上記のinitメソッドに次のコードを追加しました.
    def __init__(self):
        import time
        time.sleep(1)

プログラムを再実行すると、次の結果が得られます.
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">
<__main__.singleton object="" at="">

以上のように作成した単一の例では、マルチスレッドをサポートできないという問題が発生しました.解決策:ロックをかける.ロックされていない部分は同時に実行され、ロックされている部分は直列に実行され、速度は低下したが、データの安全を保証した.
import threading
import time

class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(0.1)

    @classmethod
    def instance(cls, *args, **kwargs):
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

threads = []
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    threads.append(t)

[t.start() for t in threads]
[t.join() for t in threads]

obj = Singleton.instance()
print("obj: ", obj)

改造new方法
class Singleton:
    instance = None
    init_flag = False

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

    def __init__(self, x):
        if Singleton.init_flag:
            return
        self.x = x
        Singleton.init_flag = True

s1 = Singleton(1)
s2 = Singleton(2)
print("s1: ", s1, "s1.x: ", s1.x)
print("s2: ", s2, "s2.x: ", s2.x)

印刷出力:
s1:  <__main__.singleton object="" at=""> s1.x:  1
s2:  <__main__.singleton object="" at=""> s2.x:  1

ロック操作コードは以下の通りです.
import threading
import time

class Singleton:
    instance = None
    init_flag = False
    _instance_lock = threading.Lock()
    _instance_lock2 = threading.Lock()
    
    
    def __new__(cls, *args, **kwargs):
        with Singleton._instance_lock:
            if cls.instance is None:
                time.sleep(0.1)
                cls.instance = super().__new__(cls)
            return cls.instance

    def __init__(self, x):
        with Singleton._instance_lock2:
            if Singleton.init_flag:
                return
            self.x = x
            Singleton.init_flag = True

def task(i):
    s = Singleton(i)
    print("s: ", s, "s.x: ", s.x)
    # print("s dir: ", dir(s))

threads = []
for i in range(10):
    t = threading.Thread(target=task, args=(1,)) 
    threads.append(t)

[t.start() for t in threads]
[t.join() for t in threads]