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' 关键字无法完全取代锁方法(如 acquire 和 release)的使用。例如,如果程序员想为 acquire 提供参数,或者锁在一个代码块中获取但在另一个代码块中释放,如下所示。
这是一个来自 Zope 的类,经过修改以使用 'synchronize' 和 'asynchronize' 关键字,并且还使用了一个显式锁池,这些锁在不同的代码块中获取和释放,因此不使用 '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 块中处理不同锁的多个 acquire 和 release 方法时遇到的许多问题。
正式语法
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 的警告。经过适当的时间后,该语法将成为标准,上述 import 语句将不起作用,任何名为 '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 使用一个带有静态方法 'make_synchronized' 的 'synchronize' 类,该方法接受一个可调用参数,并返回一个围绕该参数新创建的、同步的、可调用的“包装器”。
对 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
最后修改: 2025-02-01 08:59:27 GMT