Python の ThreadLocal 変数

高洛峰
リリース: 2016-11-08 09:54:23
オリジナル
1368 人が閲覧しました

Werkzeug WSGI ツール ライブラリとしては、いくつかの考慮事項により、Python の組み込み ThreadLocal クラスを直接使用せず、一連の Local クラスを独自に実装します。単純な Local だけでなく、これに基づいて実装された LocalStack、LocalManager、LocalProxy も含まれます。次に、これらのクラスがどのように使用されるか、その設計の本来の意図、および具体的な実装テクニックを見てみましょう。

Local クラスの設計

Werkzeug の設計者は、主に次の 2 つの理由により、Python に付属する ThreadLocal ではニーズを満たすことができないと考えています:

Werkzeug は主に同時実行要件を満たすために「ThreadLocal」を使用し、ThreadLocal はPython にのみ付属しています。スレッドベースの同時実行を実現できます。 Python には一般的なコルーチン (グリーンレット) など、他にも多くの同時実行メソッドがあるため、コルーチンをサポートできる Local オブジェクトを実装する必要があります。

WSGI は、リクエストを処理するたびに新しいスレッドが生成されることを保証しません。つまり、スレッドは再利用できます (リクエストを処理するためにスレッド プールを維持できます)。このように、werkzeug が Python 独自の ThreadLocal を使用する場合、新しいリクエストの処理には「クリーンでない (以前に処理されたリクエストに関連するデータを保存する)」スレッドが使用されます。

これら 2 つの問題を解決するために、werkzeug には Local クラスが実装されています。ローカル オブジェクトは、スレッドとコルーチンの間でデータを分離できます。さらに、特定のスレッドまたはコルーチンでのデータのクリーニングもサポートしています (リクエストの処理後に、対応するデータをクリーンアップして、次のリクエストの到着を待つことができます)。 。

それを具体的にどのように実装するか? そのアイデアは、記事「Python における ThreadLocal 変数の詳細な理解 (パート 1)」の最後で説明しました。それは、グローバル ディクショナリを作成して使用するというものです。スレッド (またはコルーチン) の識別子がキーとして使用され、対応するスレッド (またはコルーチン) のローカル データが値として使用されます。ここで werkzeug は上記のアイデアに従って実装されていますが、Python の黒魔術を使用し、最終的にユーザーに明確でシンプルなインターフェイスを提供します。

具体的な実装

Local クラスの実装は werkzeug.local にあり、バージョン 8a84b62 のコードが分析に使用されます。最初の 2 つの記事で ThreadLocal を理解することで、Local オブジェクトの特性と使用法をすでに理解しました。したがって、ここでは Local オブジェクトの使用例は示しません。コードを直接見てみましょう。

class Local(object): 
    __slots__ = ('__storage__', '__ident_func__') 
     
    def __init__(self): 
        object.__setattr__(self, '__storage__', {}) 
        object.__setattr__(self, '__ident_func__', get_ident) 
    ...
ログイン後にコピー

Local オブジェクトが多数存在する可能性があるため、Local オブジェクトが占めるスペースを節約するために、ここでは __slots__ を使用して Local が持つことができる属性をハードコーディングします:

__storage__: 値は使用される辞書です。実際のデータを保存するには、Empty として初期化します。

__ident_func__: 値は、現在のスレッドまたはコルーチンの識別子を見つけるために使用される関数です。

Local オブジェクトの実際のデータは __storage__ に保存されるため、Local 属性に対する操作は実際には __storage__ に対する操作になります。属性の取得については、ここではマジックメソッド __getattr__ を使用して、__storage__ と __ident_func__ 以外の属性取得をインターセプトし、__storage__ に格納されている現在のスレッドまたはコルーチンのデータに指示します。属性値の set または del については、それぞれ __setattr__ および __setattr__ を使用して実装されます (これらのマジック メソッドの概要については、属性制御を参照してください)。キー コードは次のとおりです:

def __getattr__(self, name): 
    try: 
        return self.__storage__[self.__ident_func__()][name] 
    except KeyError: 
        raise AttributeError(name) 
 
def __setattr__(self, name, value): 
    ident = self.__ident_func__() 
    storage = self.__storage__ 
    try: 
        storage[ident][name] = value 
    except KeyError: 
        storage[ident] = {name: value} 
 
def __delattr__(self, name): 
    try: 
        del self.__storage__[self.__ident_func__()][name] 
    except KeyError: 
        raise AttributeError(name)
ログイン後にコピー

ID 1、2、...、N を持つ N 個のスレッドまたはコルーチンがあるとします。それぞれが Local オブジェクトを使用して、独自のローカル データの一部を保存します。次に、Local オブジェクトの内容を保存します。以下の図に示すように:

Python の ThreadLocal 変数

さらに、Local クラスは、現在のスレッドまたはコルーチンによって保存されたデータを解放するための __release_local__ メソッドも提供します。

ローカル拡張インターフェイス

Werkzeug は、Local に基づいて LocalStack と LocalManager を実装し、よりフレンドリーなインターフェイス サポートを提供します。

LocalStack

LocalStackはLocalをカプセル化することでスレッド(コルーチン)に依存しないスタック構造を実装します。具体的な使用方法は以下の通りです

ls = LocalStack() 
ls.push(12) 
print ls.top    # 12 
print ls._local.__storage__ 
# {140735190843392: {'stack': [12]}}
ログイン後にコピー

LocalStackの実装は非常に興味深いものになります。ローカル オブジェクトは独自の属性 _local として使用され、インターフェイスのプッシュ、ポップ、およびトップ メソッドが定義され、対応するスタック操作を実行します。ここではリスト _local.__storage__._local.__ident_func__() を使用してスタック構造をシミュレートします。インターフェイスのプッシュ、ポップ、トップでは、このリストを操作することでスタックの動作をシミュレートします。 なお、インターフェイス関数内でこのリストを取得する場合、上記の太字ほど複雑にする必要はありません。 _local の getattr() メソッドを直接使用できます。プッシュ関数を例に取ると、実装は次のようになります:

def push(self, obj): 
    """Pushes a new item to the stack""" 
    rv = getattr(self._local, 'stack', None) 
    if rv is None: 
        self._local.stack = rv = [] 
    rv.append(obj) 
    return rv
ログイン後にコピー

pop と top の実装は一般的なスタックの実装と似ており、両方ともスタック上で対応する操作を実行します = getattr(self._local, 'stack '、なし) リスト。さらに、LocalStack では __ident_func__ をカスタマイズすることもできます。ここでは、組み込み関数プロパティを使用して記述子を生成し、__ident_func__ の get および set 操作をカプセル化し、属性値 __ident_func__ をインターフェイスとして提供します。具体的なコードは次のとおりです。 :

def _get__ident_func__(self): 
    return self._local.__ident_func__ 
 
def _set__ident_func__(self, value): 
    object.__setattr__(self._local, '__ident_func__', value) 
__ident_func__ = property(_get__ident_func__, _set__ident_func__) 
del _get__ident_func__, _set__ident_func__
ログイン後にコピー

LocalManager

Local と LocalStack は、スレッドやコルーチンから独立した単一のオブジェクトです (複数の int または同じものを整理するためにリストを使用するのと同じように) 複数の Local または LocalStack オブジェクトを整理するには、スレッドまたはコルーチンに依存しないコンテナーが必要になることがよくあります。文字列型)。

Werkzeug实现了LocalManager,它通过一个list类型的属性locals来存储所管理的Local或者LocalStack对象,还提供cleanup方法来释放所有的Local对象。Werkzeug中LocalManager最主要的接口就是装饰器方法make_middleware,代码如下:

def make_middleware(self, app): 
    """Wrap a WSGI application so that cleaning up happens after 
    request end. 
    """ 
    def application(environ, start_response): 
        return ClosingIterator(app(environ, start_response), self.cleanup) 
    return application
ログイン後にコピー

这个装饰器注册了回调函数cleanup,当一个线程(或者协程)处理完请求之后,就会调用cleanup清理它所管理的Local或者LocalStack 对象(ClosingIterator 的实现在 werkzeug.wsgi中)。下面是一个使用 LocalManager 的简单例子:

from werkzeug.local import Local, LocalManager 
 
local = Local() 
local_2 = Local() 
local_manager = LocalManager([local, local2]) 
 
def application(environ, start_response): 
    local.request = request = Request(environ) 
    ... 
 
# application 处理完毕后,会自动清理local_manager 的内容
ログイン後にコピー

通过LocalManager的make_middleware我们可以在某个线程(协程)处理完一个请求后,清空所有的Local或者LocalStack对象,这样这个线程又可以处理另一个请求了。至此,文章开始时提到的第二个问题就可以解决了。Werkzeug.local 里面还实现了一个 LocalProxy 用来作为Local对象的代理,也非常值得去学习。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート