Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 319 – Python 同步/异步代码块

作者:
Michel Pelletier <michel at users.sourceforge.net>
状态:
已拒绝
类型:
标准轨迹
创建:
2003年2月24日
Python 版本:
2.4
历史记录:


目录

摘要

此 PEP 提案向 Python 添加两个新关键字,“synchronize” 和 “asynchronize”。

宣告

此 PEP 被拒绝,取而代之的是 PEP 343

“synchronize” 关键字
Python 中代码同步的概念过于底层。要同步代码,程序员必须了解以下伪代码模式的细节
initialize_lock()

...

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

此同步代码块模式并非唯一的模式(下面将详细讨论),但它非常常见。此 PEP 提案用以下等效代码替换上述代码

synchronize:
    change_shared_data()

此方案的优点是语法更简单,用户出错的空间更小。目前,用户需要编写有关在“try/finally”块中获取和释放线程锁的代码;此代码中的错误会导致臭名昭著的难以解决的并发线程锁定问题。

“asynchronize” 关键字
在执行“synchronize”代码块时,程序员可能希望暂时“回退”到异步运行,以运行阻塞输入/输出例程或其他可能需要不确定时间且不需要同步的其他操作。此代码通常遵循以下模式
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()

代码的异步部分在视觉上不是很明显,因此用注释标记。使用提议的“asynchronize”关键字,此代码变得更加简洁、易于理解且不易出错

synchronize:
    change_shared_data()

    asynchronize:
       do_blocking_io()

    change_shared_data2()

在非同步块内遇到“asynchronize”关键字可能会引发错误或发出警告(因为所有代码块都隐式异步)。需要注意的是,以上示例**不等于**

synchronize:
    change_shared_data()

do_blocking_io()

synchronize:
    change_shared_data2()

因为两个同步代码块都可能在循环的同一迭代中运行,请考虑

while in_main_loop():
    synchronize:
        change_shared_data()

        asynchronize:
           do_blocking_io()

        change_shared_data2()

许多线程可能正在遍历此代码。如果没有“asynchronize”关键字,一个线程无法留在循环中并在同一时间释放锁,同时正在进行阻塞 IO。这种在主循环内部释放锁以进行阻塞 IO 的模式在 CPython 解释器本身中被广泛使用。

同步目标

如提议,“synchronize”和“asynchronize”关键字同步一段代码。但是,程序员可能希望指定线程同步的目标对象。任何对象都可以作为同步目标。

考虑一个双向队列对象:两个不同的对象由相同的“synchronize”代码块使用,以分别在“get”方法中同步这两个队列

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

以下是 Python 中当前等效的代码,没有“synchronize”关键字

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()

最后一个示例必须定义一个额外的类将锁与队列关联起来,而第一个示例中,“synchronize”关键字在内部和透明地进行此关联。

其他同步模式

在某些情况下,“synchronize”和“asynchronize”关键字无法完全取代使用诸如acquirerelease之类的锁方法。一些示例是,如果程序员希望为acquire提供参数,或者如果锁在一个代码块中获取但在另一个代码块中释放,如下所示。

这是一个修改为使用“synchronize”和“asynchronize”关键字的 Zope 类,并且还使用一个显式锁池,这些锁在不同的代码块中获取和释放,因此不使用“synchronize”

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()

以下是“Zope/ZServer/PubCore/ZRendevous.py”模块中找到的原始类。“_a”和“_r”快捷名称的“便利性”使代码变得模糊

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()

特别是accept方法的异步部分不是很明显。对于初学者程序员来说,“synchronize”和“asynchronize”消除了在不同锁的不同try/finally块中处理多个acquirerelease方法时遇到的许多问题。

正式语法

Python 语法在 Python 语言参考[1]中描述的修改后的 BNF 语法表示法中定义。本节使用此语法描述提议的同步语法

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

(“...”表示省略的其他复合语句)。

提议的实现

此 PEP 的作者尚未探索实现。必须解决几个实现问题。主要的实现问题是在同步块期间究竟锁定和解锁什么。

在非限定同步块(不带目标参数使用“synchronize”关键字)期间,可以创建一个锁并将其与同步代码块对象关联。任何要执行该块的线程都必须首先获取代码块锁。

当在“synchronize”块中遇到“asynchronize”关键字时,在执行内部块之前会解锁代码块锁,并在内部块终止时重新锁定。

当指定同步块目标时,该对象会与一个锁关联。如何干净地实现这一点可能是此提案风险最高的方面。Java 虚拟机通常将一个特殊的隐藏锁对象与目标对象关联,并使用它仅围绕目标对象同步块。

向后兼容性

向后兼容性通过新的from __future__ Python 语法(PEP 236)和新的警告框架(PEP 230)来解决,以使 Python 语言逐步淘汰任何使用新关键字“synchronize”和“asynchronize”的冲突名称。要立即使用此语法,开发人员可以使用以下语句

from __future__ import threadsync  # or whatever

此外,任何使用“synchronize”或“asynchronize”作为标识符的代码都将收到来自 Python 的警告。在适当的时间段后,该语法将成为标准,上述导入语句将不起作用,任何名为“synchronize”或“asynchronize”的标识符都将引发异常。

PEP 310 可靠的获取/释放对

PEP 310 提议使用“with”关键字,它可以起到与“synchronize”相同的作用(但没有“asynchronize”的功能)。模式

initialize_lock()

with the_lock:
    change_shared_data()

等效于提议的

synchronize the_lock:
    change_shared_data()

PEP 310 必须同步到现有的锁,而此 PEP 提案是,除了限定的“synchronize”语句之外,非限定的“synchronize”语句还同步到全局的、内部的、透明的锁。“with”语句还需要锁初始化,而“synchronize”语句可以同步到任何目标对象,**包括**锁。

虽然受到这种方式的限制,“with”语句比同步更抽象,并且用途更多。例如,可以使用“with”关键字进行事务

initialize_transaction()

with my_transaction:
    do_in_transaction()

# when the block terminates, the transaction is committed.

“synchronize”和“asynchronize”关键字无法服务于此或任何其他通用获取/释放模式,除了线程同步。

Java 是如何实现的

Java 定义了一个“synchronized”关键字(请注意 Java 关键字和此 PEP 的“synchronize”之间的语法时态不同),它必须限定在任何对象上。语法如下

synchronized (Expression) Block

表达式必须产生一个有效的对象(null 会引发错误,并且在“表达式”期间发生的异常会终止“synchronized”块,原因相同),在此基础上同步“块”。

Jython 是如何实现的

Jython 使用一个“synchronize”类,该类具有静态方法“make_synchronized”,该方法接受一个可调用参数并返回一个围绕参数的新创建的、同步的、可调用的“包装器”。

对 Python 提议的更改摘要

向语言中添加新的“synchronize”和“asynchronize”关键字。

风险

此 PEP 提案向 Python 语言添加两个关键字。这可能会破坏代码。

没有可供测试的实现。

这不是当今 Python 程序员面临的最重要的问题(尽管它是一个相当臭名昭著的问题)。

等效的 Java 关键字是过去分词“synchronized”。此 PEP 提案使用现在时态“synchronize”,因为它更符合 Python 的精神(Python 中编译时和运行时之间的区别比 Java 小)。

异议意见

此 PEP 未在 python-dev 上讨论。

参考文献


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

上次修改:2023-09-09 17:39:29 GMT