Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 319 – Python Synchronize/Asynchronize Block

Autor:
Michel Pelletier <michel at users.sourceforge.net>
Status:
Abgelehnt
Typ:
Standards Track
Erstellt:
24-Feb-2003
Python-Version:
2.4
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Dieser PEP schlägt die Einführung zweier neuer Schlüsselwörter in Python vor: 'synchronize' und 'asynchronize'.

Bekanntmachung

Dieser PEP wird zugunsten von PEP 343 abgelehnt.

Das Schlüsselwort 'synchronize'
Das Konzept der Code-Synchronisation in Python ist zu niedrigschwellig. Um Code zu synchronisieren, muss ein Programmierer die Details des folgenden Pseudocode-Musters kennen
initialize_lock()

...

acquire_lock()
try:
    change_shared_data()
finally:
    release_lock()

Dieses synchronisierte Blockmuster ist nicht das einzige Muster (mehr dazu weiter unten), aber es ist sehr verbreitet. Dieser PEP schlägt vor, den obigen Code durch den folgenden äquivalenten Code zu ersetzen

synchronize:
    change_shared_data()

Die Vorteile dieses Schemas sind eine einfachere Syntax und weniger Raum für Benutzerfehler. Derzeit sind Benutzer verpflichtet, Code zum Erwerb und zur Freigabe von Thread-Sperren in 'try/finally'-Blöcken zu schreiben; Fehler in diesem Code können zu notorisch schwierigen Problemen bei der gleichzeitigen Thread-Sperrung führen.

Das Schlüsselwort 'asynchronize'
Während der Ausführung eines 'synchronize'-Codeblocks möchte ein Programmierer möglicherweise kurzzeitig "zurückfallen" auf asynchrone Ausführung, um blockierende Ein-/Ausgabe-Routinen oder etwas anderes auszuführen, das eine unbestimmte Zeit dauern kann und keine Synchronisation erfordert. Dieser Code folgt normalerweise dem Muster
initialize_lock()

...

acquire_lock()
try:
    change_shared_data()
    release_lock()             # become async
    do_blocking_io()
    acquire_lock()             # sync again
    change_shared_data2()

finally:
    release_lock()

Der asynchrone Teil des Codes ist visuell nicht sehr offensichtlich, daher wird er mit Kommentaren markiert. Mit dem vorgeschlagenen 'asynchronize'-Schlüsselwort wird dieser Code sauberer, leichter verständlich und weniger fehleranfällig.

synchronize:
    change_shared_data()

    asynchronize:
       do_blocking_io()

    change_shared_data2()

Das Auftreten eines 'asynchronize'-Schlüsselworts innerhalb eines nicht synchronisierten Blocks kann entweder einen Fehler auslösen oder eine Warnung ausgeben (da alle Codeblöcke ohnehin implizit asynchron sind). Es ist wichtig zu beachten, dass das obige Beispiel **nicht** dasselbe ist wie

synchronize:
    change_shared_data()

do_blocking_io()

synchronize:
    change_shared_data2()

Da beide synchronisierten Codeblöcke innerhalb derselben Schleifeniteration ausgeführt werden können, betrachten Sie

while in_main_loop():
    synchronize:
        change_shared_data()

        asynchronize:
           do_blocking_io()

        change_shared_data2()

Viele Threads können diesen Code durchlaufen. Ohne das 'asynchronize'-Schlüsselwort kann ein Thread nicht in der Schleife bleiben und gleichzeitig den Sperrmechanismus freigeben, während blockierende E/A stattfindet. Dieses Muster, Sperrmechanismen innerhalb einer Hauptschleife zur Durchführung blockierender E/A freizugeben, wird ausgiebig im CPython-Interpreter selbst verwendet.

Synchronization Targets

Wie vorgeschlagen, synchronisieren die Schlüsselwörter 'synchronize' und 'asynchronize' einen Codeblock. Programmierer möchten jedoch möglicherweise ein Zielobjekt angeben, auf dem Threads synchronisieren. Jedes Objekt kann ein Synchronisationsziel sein.

Betrachten Sie ein zweiseitiges Warteschlangenobjekt: Zwei verschiedene Objekte werden vom selben 'synchronize'-Codeblock verwendet, um beide Warteschlangen separat in der 'get'-Methode zu synchronisieren.

class TwoWayQueue:
    def __init__(self):
        self.front = []
        self.rear = []

    def putFront(self, item):
        self.put(item, self.front)

    def getFront(self):
        item = self.get(self.front)
        return item

    def putRear(self, item):
        self.put(item, self.rear)

    def getRear(self):
        item = self.get(self.rear)
        return item

    def put(self, item, queue):
        synchronize queue:
            queue.append(item)

    def get(self, queue):
        synchronize queue:
            item = queue[0]
            del queue[0]
            return item

Hier ist der äquivalente Code in Python, wie er jetzt ohne ein 'synchronize'-Schlüsselwort ist.

import thread

class LockableQueue:

    def __init__(self):
        self.queue = []
        self.lock = thread.allocate_lock()

class TwoWayQueue:
    def __init__(self):
        self.front = LockableQueue()
        self.rear = LockableQueue()

    def putFront(self, item):
        self.put(item, self.front)

    def getFront(self):
        item = self.get(self.front)
        return item

    def putRear(self, item):
        self.put(item, self.rear)

    def getRear(self):
        item = self.get(self.rear)
        return item

    def put(self, item, queue):
        queue.lock.acquire()
        try:
            queue.append(item)
        finally:
            queue.lock.release()

    def get(self, queue):
        queue.lock.acquire()
        try:
            item = queue[0]
            del queue[0]
            return item
        finally:
            queue.lock.release()

Das letzte Beispiel musste eine zusätzliche Klasse definieren, um eine Sperre mit der Warteschlange zu verknüpfen, während im ersten Beispiel das 'synchronize'-Schlüsselwort diese Zuordnung intern und transparent vornimmt.

Other Patterns that Synchronize

Es gibt einige Situationen, in denen die Schlüsselwörter 'synchronize' und 'asynchronize' die Verwendung von Sperrmethoden wie acquire und release nicht vollständig ersetzen können. Beispiele hierfür sind, wenn der Programmierer Argumente für acquire bereitstellen möchte oder wenn eine Sperre in einem Codeblock erworben, aber in einem anderen freigegeben wird, wie unten gezeigt.

Hier ist eine Klasse aus Zope, die modifiziert wurde, um sowohl die Schlüsselwörter 'synchronize' als auch 'asynchronize' zu verwenden und außerdem einen Pool von expliziten Sperren nutzt, die in verschiedenen Codeblöcken erworben und freigegeben werden und daher 'synchronize' nicht verwenden.

import thread
from ZServerPublisher import ZServerPublisher

class ZRendevous:

    def __init__(self, n=1):
        pool=[]
        self._lists=pool, [], []

        synchronize:
            while n > 0:
                l=thread.allocate_lock()
                l.acquire()
                pool.append(l)
                thread.start_new_thread(ZServerPublisher,
                                        (self.accept,))
                n=n-1

    def accept(self):
        synchronize:
            pool, requests, ready = self._lists
            while not requests:
                l=pool[-1]
                del pool[-1]
                ready.append(l)

                asynchronize:
                    l.acquire()

                pool.append(l)

            r=requests[0]
            del requests[0]
            return r

    def handle(self, name, request, response):
        synchronize:
            pool, requests, ready = self._lists
            requests.append((name, request, response))
            if ready:
                l=ready[-1]
                del ready[-1]
                l.release()

Hier ist die Originalklasse, wie sie im Modul 'Zope/ZServer/PubCore/ZRendevous.py' zu finden ist. Die "Bequemlichkeit" der Kurznamen '_a' und '_r' verschleiert den Code.

import thread
from ZServerPublisher import ZServerPublisher

class ZRendevous:

    def __init__(self, n=1):
        sync=thread.allocate_lock()
        self._a=sync.acquire
        self._r=sync.release
        pool=[]
        self._lists=pool, [], []
        self._a()
        try:
            while n > 0:
                l=thread.allocate_lock()
                l.acquire()
                pool.append(l)
                thread.start_new_thread(ZServerPublisher,
                                        (self.accept,))
                n=n-1
        finally: self._r()

    def accept(self):
        self._a()
        try:
            pool, requests, ready = self._lists
            while not requests:
                l=pool[-1]
                del pool[-1]
                ready.append(l)
                self._r()
                l.acquire()
                self._a()
                pool.append(l)

            r=requests[0]
            del requests[0]
            return r
        finally: self._r()

    def handle(self, name, request, response):
        self._a()
        try:
            pool, requests, ready = self._lists
            requests.append((name, request, response))
            if ready:
                l=ready[-1]
                del ready[-1]
                l.release()
        finally: self._r()

Insbesondere ist der asynchrone Abschnitt der Methode accept nicht sehr offensichtlich. Für Anfänger entfernen 'synchronize' und 'asynchronize' viele der Probleme, die beim Jonglieren mit mehreren acquire- und release-Methoden für verschiedene Sperren in verschiedenen try/finally-Blöcken auftreten.

Formal Syntax

Die Python-Syntax wird in einer modifizierten BNF-Grammatiknotation beschrieben, die in der Python Language Reference [1] erläutert wird. Dieser Abschnitt beschreibt die vorgeschlagene Synchronisationssyntax mit dieser Grammatik.

synchronize_stmt: 'synchronize' [test] ':' suite
asynchronize_stmt: 'asynchronize' [test] ':' suite
compound_stmt: ... | synchronized_stmt | asynchronize_stmt

(Die '...' zeigen andere unterdrückte zusammengesetzte Anweisungen an).

Vorgeschlagene Implementierung

Der Autor dieses PEP hat noch keine Implementierung untersucht. Es gibt mehrere Implementierungsfragen, die gelöst werden müssen. Die wichtigste Implementierungsfrage ist, was genau während eines synchronisierten Blocks gesperrt und entsperrt wird.

Während eines nicht qualifizierten synchronisierten Blocks (Verwendung des Schlüsselworts 'synchronize' ohne Zielargument) könnte eine Sperre erstellt und dem synchronisierten Codeblockobjekt zugeordnet werden. Alle Threads, die den Block ausführen sollen, müssen zuerst die Codeblock-Sperre erwerben.

Beim Auftreten eines 'asynchronize'-Schlüsselworts in einem 'synchronize'-Block wird die Codeblock-Sperre vor der Ausführung des inneren Blocks entsperrt und nach Beendigung des inneren Blocks wieder gesperrt.

Wenn ein Ziel für einen synchronisierten Block angegeben wird, wird das Objekt mit einer Sperre verknüpft. Wie dies sauber implementiert wird, ist wahrscheinlich das größte Risiko dieses Vorschlags. Java Virtual Machines ordnen typischerweise ein spezielles verstecktes Sperrobjekt dem Zielobjekt zu und verwenden es, um den Block um das Ziel herum zu synchronisieren.

Abwärtskompatibilität

Abwärtskompatibilität wird mit der neuen from __future__ Python-Syntax (PEP 236) und dem neuen Warnsystem (PEP 230) gelöst, um die Python-Sprache weiterzuentwickeln und widersprüchliche Namen, die die neuen Schlüsselwörter 'synchronize' und 'asynchronize' verwenden, auslaufen zu lassen. Um die Syntax jetzt zu verwenden, könnte ein Entwickler die Anweisung verwenden

from __future__ import threadsync  # or whatever

Darüber hinaus wird jeder Code, der das Schlüsselwort 'synchronize' oder 'asynchronize' als Bezeichner verwendet, mit einer Warnung von Python versehen. Nach angemessener Zeit würde die Syntax zum Standard, die obige Importanweisung würde nichts tun, und alle Bezeichner mit dem Namen 'synchronize' oder 'asynchronize' würden eine Ausnahme auslösen.

PEP 310 Reliable Acquisition/Release Pairs

PEP 310 schlägt das Schlüsselwort 'with' vor, das dieselbe Funktion wie 'synchronize' (aber keine Möglichkeit für 'asynchronize') erfüllen kann. Das Muster

initialize_lock()

with the_lock:
    change_shared_data()

ist äquivalent zu dem vorgeschlagenen

synchronize the_lock:
    change_shared_data()

PEP 310 muss auf einer vorhandenen Sperre synchronisieren, während dieser PEP vorschlägt, dass nicht qualifizierte 'synchronize'-Anweisungen auf einer globalen, internen, transparenten Sperre synchronisieren, zusätzlich zu qualifizierten 'synchronize'-Anweisungen. Die 'with'-Anweisung erfordert auch eine Sperrinitialisierung, während die 'synchronize'-Anweisung auf jedem Zielobjekt synchronisieren kann, **einschließlich** von Sperren.

Obwohl auf diese Weise begrenzt, ist die 'with'-Anweisung abstrakter und dient mehr Zwecken als der Synchronisation. Zum Beispiel könnten Transaktionen mit dem 'with'-Schlüsselwort verwendet werden.

initialize_transaction()

with my_transaction:
    do_in_transaction()

# when the block terminates, the transaction is committed.

Die Schlüsselwörter 'synchronize' und 'asynchronize' können dieses oder jedes andere allgemeine Erwerbe/Freigabe-Muster außer der Thread-Synchronisation nicht ersetzen.

How Java Does It

Java definiert ein Schlüsselwort 'synchronized' (beachten Sie die unterschiedliche grammatikalische Form zwischen dem Java-Schlüsselwort und dem 'synchronize' dieses PEP), das auf jedem Objekt qualifiziert sein muss. Die Syntax ist

synchronized (Expression) Block

Expression muss ein gültiges Objekt ergeben (null löst einen Fehler aus, und Ausnahmen während 'Expression' beenden den 'synchronized'-Block aus denselben Gründen), auf dem 'Block' synchronisiert wird.

How Jython Does It

Jython verwendet eine Klasse 'synchronize' mit der statischen Methode 'make_synchronized', die ein aufrufbares Argument annimmt und eine neu erstellte, synchronisierte, aufrufbare "Wrapper"-Version des Arguments zurückgibt.

Summary of Proposed Changes to Python

Hinzufügen neuer Schlüsselwörter 'synchronize' und 'asynchronize' zur Sprache.

Risiken

Dieser PEP schlägt die Einführung zweier Schlüsselwörter in die Python-Sprache vor. Dies kann zu Kompatibilitätsproblemen mit vorhandenem Code führen.

Es gibt keine Implementierung zum Testen.

Es ist nicht das wichtigste Problem, mit dem Python-Programmierer heute konfrontiert sind (obwohl es ein ziemlich berüchtigtes ist).

Das äquivalente Java-Schlüsselwort ist das Partizip Perfekt 'synchronized'. Dieser PEP schlägt die Gegenwartsform 'synchronize' vor, da sie stärker im Geiste von Python liegt (da es in Python weniger Unterscheidung zwischen Kompilierzeit und Laufzeit gibt als in Java).

Abweichende Meinung

Dieser PEP wurde nicht auf python-dev diskutiert.

Referenzen


Source: https://github.com/python/peps/blob/main/peps/pep-0319.rst

Last modified: 2025-02-01 08:59:27 GMT