ThreadLocal-Variablen in Python

高洛峰
Freigeben: 2016-11-08 09:54:23
Original
1339 Leute haben es durchsucht

Als WSGI-Toolbibliothek verwendet Werkzeug aus bestimmten Gründen nicht direkt die in Python integrierte ThreadLocal-Klasse, sondern implementiert selbst eine Reihe lokaler Klassen. Einschließlich einfacher Local sowie darauf basierend implementierter LocalStack, LocalManager und LocalProxy. Als nächstes werfen wir einen Blick auf die Verwendung dieser Klassen, die ursprüngliche Absicht ihres Designs und spezifische Implementierungstechniken.

Design der Local-Klasse

Die Designer von Werkzeug glauben, dass das mit Python gelieferte ThreadLocal die Anforderungen nicht erfüllen kann, hauptsächlich aus den folgenden zwei Gründen:

Werkzeug verwendet hauptsächlich „ThreadLocal“ Um die Parallelitätsanforderungen zu erfüllen, kann Pythons eigenes ThreadLocal nur Thread-basierte Parallelität implementieren. Es gibt viele andere Parallelitätsmethoden in Python, z. B. gängige Coroutinen (Greenlets). Daher ist es erforderlich, ein lokales Objekt zu implementieren, das Coroutinen unterstützen kann.

WSGI garantiert nicht, dass jedes Mal ein neuer Thread zur Bearbeitung der Anfrage generiert wird, was bedeutet, dass der Thread wiederverwendet werden kann (ein Thread-Pool kann zur Bearbeitung der Anfrage verwaltet werden). Wenn Werkzeug Pythons eigenes ThreadLocal verwendet, wird auf diese Weise ein „unsauberer“ Thread (der Daten im Zusammenhang mit zuvor verarbeiteten Anforderungen speichert) zum Verarbeiten neuer Anforderungen verwendet.

Um diese beiden Probleme zu lösen, wird die Local-Klasse in werkzeug implementiert. Lokale Objekte können Daten zwischen Threads und Coroutinen isolieren. Darüber hinaus unterstützen sie auch die Bereinigung von Daten unter einem bestimmten Thread oder einer Coroutine (sodass Sie nach der Verarbeitung einer Anfrage die entsprechenden Daten bereinigen und dann auf die Ankunft der nächsten Anfrage warten können). .

Wie implementiert man es konkret? Die Idee ist eigentlich sehr einfach. Wir haben sie am Ende des Artikels „Detailliertes Verständnis von ThreadLocal-Variablen in Python (Teil 1)“ erwähnt Wörterbuch und fügen Sie dann den Thread (oder die Coroutine) hinzu. Der Bezeichner dient als Schlüssel und die lokalen Daten des entsprechenden Threads (oder der Coroutine) dienen als Wert. Hier wird Werkzeug gemäß den oben genannten Ideen implementiert, verwendet jedoch etwas schwarze Magie von Python und bietet Benutzern schließlich eine klare und einfache Benutzeroberfläche.

Spezifische Implementierung

Die Implementierung der Local-Klasse befindet sich in werkzeug.local und der Code der Version 8a84b62 wird zur Analyse verwendet. Durch das Verständnis von ThreadLocal in den ersten beiden Artikeln kennen wir bereits die Eigenschaften und Verwendung lokaler Objekte. Hier geben wir also keine Beispiele mehr für die Verwendung lokaler Objekte, schauen wir uns den Code direkt an.

class Local(object): 
    __slots__ = ('__storage__', '__ident_func__') 
     
    def __init__(self): 
        object.__setattr__(self, '__storage__', {}) 
        object.__setattr__(self, '__ident_func__', get_ident) 
    ...
Nach dem Login kopieren

Da es möglicherweise eine große Anzahl lokaler Objekte gibt, wird __slots__ hier verwendet, um die Attribute, die Local haben kann, fest zu codieren, um den von lokalen Objekten belegten Platz zu sparen:

__storage__: Der Wert ist ein Wörterbuch, das zum Speichern tatsächlicher Daten verwendet wird und leer initialisiert wird.

__ident_func__: Der Wert ist eine Funktion, die zum Ermitteln der Kennung des aktuellen Threads oder der aktuellen Coroutine verwendet wird.

Da die tatsächlichen Daten des Local-Objekts in __storage__ gespeichert sind, sind Operationen am Local-Attribut tatsächlich Operationen an __storage__. Zum Abrufen von Attributen wird hier die magische Methode __getattr__ verwendet, um die Attributerfassung außer __storage__ und __ident_func__ abzufangen und sie an die in __storage__ gespeicherten Daten des aktuellen Threads oder der aktuellen Coroutine weiterzuleiten. Was die Menge oder Del von Attributwerten angeht, werden sie mit __setattr__ bzw. __setattr__ implementiert (eine Einführung in diese magischen Methoden finden Sie unter Attributsteuerung). Der Schlüsselcode lautet wie folgt:

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)
Nach dem Login kopieren

Angenommen, wir haben N Threads oder Coroutinen mit den IDs 1, 2, ..., N. Jeder verwendet ein lokales Objekt, um einige seiner eigenen lokalen Daten zu speichern. dann ist der Inhalt des Local-Objekts wie folgt:

ThreadLocal-Variablen in Python

Darüber hinaus stellt die Local-Klasse auch die Methode __release_local__ bereit, um die vom aktuellen Thread oder der Coroutine gespeicherten Daten freizugeben.

Lokale Erweiterungsschnittstelle

Werkzeug implementiert LocalStack und LocalManager basierend auf Local, um eine benutzerfreundlichere Schnittstellenunterstützung bereitzustellen.

LocalStack

LocalStack implementiert eine Thread- (oder Coroutine-) unabhängige Stapelstruktur durch Kapselung von Local. Ein einfaches Verwendungsbeispiel ist wie folgt

Die Implementierung von LocalStack ist sehr interessant. Sie verwendet ein lokales Objekt als eigenes Attribut _local und definiert dann die Schnittstellenmethoden push, pop und top, um entsprechende Stapeloperationen auszuführen. Zur Simulation der Stack-Struktur wird hier die Liste _local.__storage__._local.__ident_func__() verwendet. In der Push-, Pop- und Top-Schnittstelle wird der Betrieb des Stapels durch Betreiben dieser Liste simuliert. Es ist zu beachten, dass das Abrufen dieser Liste innerhalb der Schnittstellenfunktion nicht so kompliziert sein muss wie oben Verwenden Sie direkt die getattr()-Methode von _local. Am Beispiel der Push-Funktion ist die Implementierung wie folgt:
ls = LocalStack() 
ls.push(12) 
print ls.top    # 12 
print ls._local.__storage__ 
# {140735190843392: {'stack': [12]}}
Nach dem Login kopieren

Die Implementierung von pop und top ähnelt der des allgemeinen Stapels, die beide der Liste stack = getattr(self) entsprechen ._local, 'stack', None) funktionieren. Darüber hinaus können wir mit LocalStack auch __ident_func__ anpassen. Hier verwenden wir die integrierte Funktionseigenschaft, um einen Deskriptor zu generieren, die Get- und Set-Operationen von __ident_func__ zu kapseln und einen Attributwert __ident_func__ bereitzustellen. Der spezifische Code lautet wie folgt :
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
Nach dem Login kopieren

LocalManager
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__
Nach dem Login kopieren

Local und LocalStack sind einzelne Objekte, die von Threads oder Coroutinen unabhängig sind. Oft benötigen wir einen Thread- oder Coroutine-unabhängigen Container, um mehrere Local- oder LocalStack-Objekte zu organisieren (genau wie Wir verwenden eine Liste, um mehrere Int- oder String-Typen zu organisieren.

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
Nach dem Login kopieren

这个装饰器注册了回调函数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 的内容
Nach dem Login kopieren

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

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!