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
提供参数,或者如果锁在一个代码块中获取但在另一个代码块中释放,如下所示。
这是一个修改为使用“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
块中处理多个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 的警告。在适当的时间段后,该语法将成为标准,上述导入语句将不起作用,任何名为“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