python-peps/peps/pep-0319.rst

507 lines
15 KiB
ReStructuredText
Raw Permalink Normal View History

2003-06-14 18:40:36 -04:00
PEP: 319
Title: Python Synchronize/Asynchronize Block
Version: $Revision$
Last-Modified: $Date$
Author: Michel Pelletier <michel@users.sourceforge.net>
Status: Rejected
2003-06-14 18:40:36 -04:00
Type: Standards Track
2017-08-18 14:56:24 -04:00
Content-Type: text/x-rst
2003-06-14 18:40:36 -04:00
Created: 24-Feb-2003
Python-Version: 2.4
Post-History:
2003-06-14 18:40:36 -04:00
Abstract
2017-08-18 14:56:24 -04:00
========
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
This PEP proposes adding two new keywords to Python, 'synchronize'
and 'asynchronize'.
2003-06-14 18:40:36 -04:00
2005-06-28 04:31:09 -04:00
Pronouncement
2017-08-18 14:56:24 -04:00
=============
2005-06-28 04:31:09 -04:00
This PEP is rejected in favor of :pep:`343`.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
The 'synchronize' Keyword
2003-06-14 18:40:36 -04:00
The concept of code synchronization in Python is too low-level.
To synchronize code a programmer must be aware of the details of
2017-08-18 14:56:24 -04:00
the following pseudo-code pattern::
2003-06-14 18:40:36 -04:00
initialize_lock()
...
acquire_lock()
try:
change_shared_data()
finally:
release_lock()
This synchronized block pattern is not the only pattern (more
discussed below) but it is very common. This PEP proposes
2017-08-18 14:56:24 -04:00
replacing the above code with the following equivalent::
2003-06-14 18:40:36 -04:00
synchronize:
change_shared_data()
The advantages of this scheme are simpler syntax and less room for
user error. Currently users are required to write code about
acquiring and releasing thread locks in 'try/finally' blocks;
errors in this code can cause notoriously difficult concurrent
thread locking issues.
2017-08-18 14:56:24 -04:00
The 'asynchronize' Keyword
While executing a 'synchronize' block of code a programmer may
2003-06-14 18:40:36 -04:00
want to "drop back" to running asynchronously momentarily to run
blocking input/output routines or something else that might take an
2003-06-14 18:40:36 -04:00
indeterminate amount of time and does not require synchronization.
2017-08-18 14:56:24 -04:00
This code usually follows the pattern::
2003-06-14 18:40:36 -04:00
initialize_lock()
...
acquire_lock()
try:
2003-06-14 18:40:36 -04:00
change_shared_data()
release_lock() # become async
do_blocking_io()
acquire_lock() # sync again
change_shared_data2()
finally:
release_lock()
The asynchronous section of the code is not very obvious visually,
so it is marked up with comments. Using the proposed
'asynchronize' keyword this code becomes much cleaner, easier to
2017-08-18 14:56:24 -04:00
understand, and less prone to error::
2003-06-14 18:40:36 -04:00
synchronize:
change_shared_data()
asynchronize:
do_blocking_io()
change_shared_data2()
2017-08-18 14:56:24 -04:00
Encountering an 'asynchronize' keyword inside a non-synchronized
2003-06-14 18:40:36 -04:00
block can raise either an error or issue a warning (as all code
blocks are implicitly asynchronous anyway). It is important to
2017-08-18 14:56:24 -04:00
note that the above example is **not** the same as::
2003-06-14 18:40:36 -04:00
synchronize:
change_shared_data()
do_blocking_io()
synchronize:
change_shared_data2()
Because both synchronized blocks of code may be running inside the
2017-08-18 14:56:24 -04:00
same iteration of a loop, Consider::
2003-06-14 18:40:36 -04:00
while in_main_loop():
synchronize:
change_shared_data()
asynchronize:
do_blocking_io()
change_shared_data2()
Many threads may be looping through this code. Without the
'asynchronize' keyword one thread cannot stay in the loop and
release the lock at the same time while blocking IO is going on.
This pattern of releasing locks inside a main loop to do blocking
IO is used extensively inside the CPython interpreter itself.
Synchronization Targets
2017-08-18 14:56:24 -04:00
=======================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
As proposed the 'synchronize' and 'asynchronize' keywords
synchronize a block of code. However programmers may want to
specify a target object that threads synchronize on. Any object
can be a synchronization target.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Consider a two-way queue object: two different objects are used by
the same 'synchronize' code block to synchronize both queues
separately in the 'get' method::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
class TwoWayQueue:
def __init__(self):
self.front = []
self.rear = []
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def putFront(self, item):
self.put(item, self.front)
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def getFront(self):
item = self.get(self.front)
return item
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def putRear(self, item):
self.put(item, self.rear)
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def getRear(self):
item = self.get(self.rear)
return item
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def put(self, item, queue):
synchronize queue:
queue.append(item)
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def get(self, queue):
synchronize queue:
item = queue[0]
del queue[0]
return item
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Here is the equivalent code in Python as it is now without a
'synchronize' keyword::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
import thread
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
class LockableQueue:
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def __init__(self):
self.queue = []
self.lock = thread.allocate_lock()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
class TwoWayQueue:
def __init__(self):
self.front = LockableQueue()
self.rear = LockableQueue()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def putFront(self, item):
self.put(item, self.front)
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def getFront(self):
item = self.get(self.front)
return item
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def putRear(self, item):
self.put(item, self.rear)
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def getRear(self):
item = self.get(self.rear)
return item
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def put(self, item, queue):
queue.lock.acquire()
try:
queue.append(item)
finally:
queue.lock.release()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def get(self, queue):
queue.lock.acquire()
try:
item = queue[0]
del queue[0]
return item
finally:
queue.lock.release()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
The last example had to define an extra class to associate a lock
with the queue where the first example the 'synchronize' keyword
does this association internally and transparently.
2003-06-14 18:40:36 -04:00
Other Patterns that Synchronize
2017-08-18 14:56:24 -04:00
===============================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
There are some situations where the 'synchronize' and
'asynchronize' keywords cannot entirely replace the use of lock
methods like ``acquire`` and ``release``. Some examples are if the
programmer wants to provide arguments for ``acquire`` or if a lock
is acquired in one code block but released in another, as shown
below.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Here is a class from Zope modified to use both the 'synchronize'
and 'asynchronize' keywords and also uses a pool of explicit locks
that are acquired and released in different code blocks and thus
don't use 'synchronize'::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
import thread
from ZServerPublisher import ZServerPublisher
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
class ZRendevous:
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
def __init__(self, n=1):
pool=[]
self._lists=pool, [], []
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
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:
2003-06-14 18:40:36 -04:00
l.acquire()
2017-08-18 14:56:24 -04:00
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()
Here is the original class as found in the
'Zope/ZServer/PubCore/ZRendevous.py' module. The "convenience" of
the '_a' and '_r' shortcut names obscure the 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()
In particular the asynchronize section of the ``accept`` method is
not very obvious. To beginner programmers, 'synchronize' and
'asynchronize' remove many of the problems encountered when
juggling multiple ``acquire`` and ``release`` methods on different
locks in different ``try/finally`` blocks.
2003-06-14 18:40:36 -04:00
Formal Syntax
2017-08-18 14:56:24 -04:00
=============
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Python syntax is defined in a modified BNF grammar notation
described in the Python Language Reference [1]_. This section
describes the proposed synchronization syntax using this grammar::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
synchronize_stmt: 'synchronize' [test] ':' suite
asynchronize_stmt: 'asynchronize' [test] ':' suite
compound_stmt: ... | synchronized_stmt | asynchronize_stmt
2017-08-18 14:56:24 -04:00
(The '...' indicates other compound statements elided).
2003-06-14 18:40:36 -04:00
Proposed Implementation
2017-08-18 14:56:24 -04:00
=======================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
The author of this PEP has not explored an implementation yet.
There are several implementation issues that must be resolved.
The main implementation issue is what exactly gets locked and
unlocked during a synchronized block.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
During an unqualified synchronized block (the use of the
'synchronize' keyword without a target argument) a lock could be
created and associated with the synchronized code block object.
Any threads that are to execute the block must first acquire the
code block lock.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
When an 'asynchronize' keyword is encountered in a 'synchronize'
block the code block lock is unlocked before the inner block is
executed and re-locked when the inner block terminates.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
When a synchronized block target is specified the object is
associated with a lock. How this is implemented cleanly is
probably the highest risk of this proposal. Java Virtual Machines
typically associate a special hidden lock object with target
object and use it to synchronized the block around the target
only.
2003-06-14 18:40:36 -04:00
Backward Compatibility
2017-08-18 14:56:24 -04:00
======================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Backward compatibility is solved with the new ``from __future__``
Python syntax (:pep:`236`), and the new warning framework (:pep:`230`)
to evolve the
2017-08-18 14:56:24 -04:00
Python language into phasing out any conflicting names that use
the new keywords 'synchronize' and 'asynchronize'. To use the
syntax now, a developer could use the statement::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
from __future__ import threadsync # or whatever
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
In addition, any code that uses the keyword 'synchronize' or
'asynchronize' as an identifier will be issued a warning from
Python. After the appropriate period of time, the syntax would
become standard, the above import statement would do nothing, and
any identifiers named 'synchronize' or 'asynchronize' would raise
an exception.
2003-06-14 18:40:36 -04:00
PEP 310 Reliable Acquisition/Release Pairs
2017-08-18 14:56:24 -04:00
==========================================
2003-06-14 18:40:36 -04:00
:pep:`310` proposes the 'with' keyword that can serve the same
2017-08-18 14:56:24 -04:00
function as 'synchronize' (but no facility for 'asynchronize').
The pattern::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
initialize_lock()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
with the_lock:
change_shared_data()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
is equivalent to the proposed::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
synchronize the_lock:
change_shared_data()
2003-06-14 18:40:36 -04:00
:pep:`310` must synchronize on an existing lock, while this PEP
2017-08-18 14:56:24 -04:00
proposes that unqualified 'synchronize' statements synchronize on
2019-07-03 14:20:45 -04:00
a global, internal, transparent lock in addition to qualified
2017-08-18 14:56:24 -04:00
'synchronize' statements. The 'with' statement also requires lock
initialization, while the 'synchronize' statement can synchronize
on any target object **including** locks.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
While limited in this fashion, the 'with' statement is more
abstract and serves more purposes than synchronization. For
example, transactions could be used with the 'with' keyword::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
initialize_transaction()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
with my_transaction:
do_in_transaction()
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
# when the block terminates, the transaction is committed.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
The 'synchronize' and 'asynchronize' keywords cannot serve this or
any other general acquire/release pattern other than thread
synchronization.
2003-06-14 18:40:36 -04:00
How Java Does It
2017-08-18 14:56:24 -04:00
================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Java defines a 'synchronized' keyword (note the grammatical tense
different between the Java keyword and this PEP's 'synchronize')
which must be qualified on any object. The syntax is::
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
synchronized (Expression) Block
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Expression must yield a valid object (null raises an error and
exceptions during 'Expression' terminate the 'synchronized' block
for the same reason) upon which 'Block' is synchronized.
2003-06-14 18:40:36 -04:00
How Jython Does It
2017-08-18 14:56:24 -04:00
==================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Jython uses a 'synchronize' class with the static method
'make_synchronized' that accepts one callable argument and returns
a newly created, synchronized, callable "wrapper" around the
argument.
2003-06-14 18:40:36 -04:00
Summary of Proposed Changes to Python
2017-08-18 14:56:24 -04:00
=====================================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
Adding new 'synchronize' and 'asynchronize' keywords to the
language.
2003-06-14 18:40:36 -04:00
Risks
2017-08-18 14:56:24 -04:00
=====
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
This PEP proposes adding two keywords to the Python language. This
may break code.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
There is no implementation to test.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
It's not the most important problem facing Python programmers
today (although it is a fairly notorious one).
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
The equivalent Java keyword is the past participle 'synchronized'.
This PEP proposes the present tense, 'synchronize' as being more
in spirit with Python (there being less distinction between
compile-time and run-time in Python than Java).
2003-06-14 18:40:36 -04:00
Dissenting Opinion
2017-08-18 14:56:24 -04:00
==================
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
This PEP has not been discussed on python-dev.
2003-06-14 18:40:36 -04:00
References
2017-08-18 14:56:24 -04:00
==========
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
.. [1] The Python Language Reference
http://docs.python.org/reference/
2003-06-14 18:40:36 -04:00
Copyright
2017-08-18 14:56:24 -04:00
=========
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
This document has been placed in the public domain.
2003-06-14 18:40:36 -04:00
2017-08-18 14:56:24 -04:00
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
End: