Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (30 page)

BOOK: Programming Python
11.02Mb size Format: txt, pdf, ePub
ads
More on the Global Interpreter Lock

Although it’s a
lower-level topic than you generally need to do useful
thread work in Python, the implementation of Python’s threads can have
impacts on both performance and coding. This section summarizes
implementation details and
some of their
ramifications
.

Note

Threads implementation in the upcoming Python
3.2
: This section describes the current implementation of
threads up to and including Python 3.1. At this writing, Python 3.2 is
still in development, but one of its likely enhancements is a new
version of the GIL that provides better performance, especially on
some multicore CPUs. The new GIL implementation will still synchronize
access to the PVM (Python language code is still multiplexed as
before), but it will use a context switching scheme that is more
efficient than the current N-bytecode-instruction approach.

Among other things, the current
sys.setcheckinterval
call will likely be
replaced with a timer duration call in the new scheme. Specifically,
the concept of a check interval for thread switches will be abandoned
and replaced by an absolute time duration expressed in seconds. It’s
anticipated that this duration will default to 5 milliseconds, but it
will be tunable through
sys.setswitchinterval
.

Moreover, there have been a variety of plans made to remove the
GIL altogether (including goals of the Unladen Swallow project being
conducted by Google employees), though none have managed to produce
any fruit thus far. Since I cannot predict the future, please see
Python release documents to follow this (well…) thread.

Strictly speaking, Python currently uses the
global
interpreter lock
(GIL) mechanism introduced at the start of
this section, which guarantees that one thread, at most, is running code
within the Python interpreter at any given point in time. In addition,
to make sure that each thread gets a chance to run, the interpreter
automatically switches its attention between threads at regular
intervals (in Python 3.1, by releasing and acquiring the lock after a
number of bytecode instructions) as well as at the start of long-running
operations (e.g., on some file input/output requests).

This scheme avoids problems that could arise if multiple threads
were to update Python system data at the same time. For instance, if two
threads were allowed to simultaneously change an object’s reference
count, the result might be unpredictable. This scheme can also have
subtle consequences. In this chapter’s threading examples, for instance,
the
stdout
stream can be corrupted
unless each thread’s call to write text is synchronized with thread
locks.

Moreover, even though the GIL prevents more than one Python thread
from running at the same time, it is not enough to ensure thread safety
in general, and it does not address higher-level synchronization issues
at all. For example, as we saw, when more than one thread might attempt
to
update
the same variable at the same time, the
threads should generally be given exclusive access to the object with
locks. Otherwise, it’s not impossible that thread switches will occur in
the middle of an update statement’s
bytecode
.

Locks are not strictly required for all shared object access,
especially if a single thread updates an object inspected by other
threads. As a rule of thumb, though, you should generally use locks to
synchronize threads whenever update rendezvous are possible instead of
relying on artifacts of the current thread
implementation.

The thread switch interval

Some concurrent
updates might work without locks if the thread-switch
interval is set high enough to allow each thread to finish without
being swapped out. The
sys.
set
check
interval(N)
call sets the frequency
with which the interpreter checks for things like thread switches and
signal handlers.

This interval defines the number of bytecode instructions before
a switch. It does not need to be reset for most programs, but it can
be used to tune thread performance. Setting higher values means
switches happen less often: threads incur less overhead but they are
less responsive to events. Setting lower values makes threads more
responsive to events but increases thread switch overhead.

Atomic operations

Because of the way
Python uses the GIL to synchronize threads’ access to
the virtual machine, whole statements are not generally thread-safe,
but each bytecode instruction is. Because of this bytecode
indivisibility, some Python language operations are thread-safe—also
called
atomic
, because they run without
interruption—and do not require the use of locks or queues to avoid
concurrent update issues. For instance, as of this writing,
list.append
, fetches and some assignments
for variables, list items, dictionary keys, and object attributes, and
other operations were still atomic in standard C Python; others, such
as
x = x+1
(and any operation in
general that reads data, modifies it, and writes it back) were
not.

As mentioned earlier, though, relying on these rules is a bit of
a gamble, because they require a deep understanding of Python
internals and may vary per release. Indeed, the set of atomic
operations may be radically changed if a new free-threaded
implementation ever appears. As a rule of thumb, it may be easier to
use locks for all access to global and shared objects than to try to
remember which types of access may or may not be safe across multiple
threads.

C API thread considerations

Finally, if you
plan to mix Python with C, also see the thread
interfaces described in the Python/C API standard
manual. In threaded programs, C extensions must release
and reacquire the GIL around long-running operations to let the Python
language portions of other Python threads run during the wait.
Specifically, the long-running C extension function should release the
lock on entry and reacquire it on exit when resuming Python
code.

Also note that even though the Python code in Python threads
cannot truly overlap in time due to the GIL synchronization, the
C-coded portions of threads can. Any number may be running in
parallel, as long as they do work outside the scope of the Python
virtual machine. In fact, C threads may overlap both with other C
threads and with Python language threads run in the virtual machine.
Because of this, splitting code off to C libraries is one way that
Python applications can still take advantage of multi-CPU
machines.

Still, it may often be easier to leverage such machines by
simply writing Python programs that fork processes instead of starting
threads. The complexity of process and thread code is similar. For
more on C extensions and their threading requirements, see
Chapter 20
. In short, Python includes C
language tools (including a pair of GIL management macros) that can be
used to wrap long-running operations in C-coded extensions and that
allow other Python language threads to run in parallel.

A process-based alternative: multiprocessing (ahead)

By now, you
should have a basic grasp of parallel processes and
threads, and Python’s tools that support them. Later in this chapter,
we’ll revisit both ideas to study the
multiprocessing
module—a standard library tool that seeks to combine the
simplicity and portability of threads with the benefits of processes,
by implementing a
threading
-like
API that runs processes instead of threads. It seeks to address the
portability issue of processes, as well as the multiple-CPU
limitations imposed in threads by the GIL, but it cannot be used as a
replacement for forking in some contexts, and it imposes some
constraints that threads do not, which stem from its process-based
model (for instance, mutable object state is not directly shared
because objects are copied across process boundaries, and unpickleable
objects such as bound methods cannot be as freely used).

Because the
multiprocessing
module also implements tools to simplify tasks such as inter-process
communication and exit status, though, let’s first get a handle on
Python’s support in those domains as well, and explore some more
process and thread examples along the
way.

[
14
]
The
_thread
examples in
this book now all use
start_new_thread
. This call is also
available as
thread.start_new
for historical reasons, but this synonym may be removed in a
future Python release. As of Python 3.1, both names are still
available, but the
help
documentation for
start_new
claims that it is obsolete; in other words, you should probably
prefer the other if you care about the future (and this book
must!).

[
15
]
They cannot, however, be used to directly synchronize
processes
. Since processes are more
independent, they usually require locking mechanisms that are more
long-lived and external to programs.
Chapter 4
’s
os.open
call with an open flag of
O_EXCL
allows scripts to lock
and unlock files and so is ideal as a cross-process locking tool.
See also the synchronization tools in the
multiprocessing
and
threading
modules and the IPC section
later in this chapter for other general synchronization
ideas.

[
16
]
But in case this means you, Python’s lock and condition
variables are distinct objects, not something inherent in all
objects, and Python’s
Thread
class doesn’t have all the features of Java’s. See Python’s library
manual for further details.

Program Exits

As we’ve seen,
unlike C, there is no “main” function in Python. When we run
a program, we simply execute all of the code in the top-level file, from
top to bottom (i.e., in the filename we listed in the command line,
clicked in a file explorer, and so on). Scripts normally exit when Python
falls off the end of the file, but we may also call for program exit
explicitly with tools in the
sys
and
os
modules.

sys Module Exits

For example, the
built-in
sys.exit
function ends a program when called, and earlier than normal:

>>>
sys.exit(N)
# exit with status N, else exits on end of script

Interestingly, this call really just raises the built-in
SystemExit
exception. Because of this, we can
catch it as usual to intercept early exits and perform cleanup
activities; if uncaught, the interpreter exits as usual. For
instance:

C:\...\PP4E\System>
python
>>>
import sys
>>>
try:
...
sys.exit()
# see also: os._exit, Tk().quit()
...
except SystemExit:
...
print('ignoring exit')
...
ignoring exit
>>>

Programming tools such as debuggers can make use of this hook to
avoid shutting down. In fact, explicitly raising the built-in
SystemExit
exception with a Python
raise
statement is equivalent to calling
sys.exit
. More realistically, a
try
block would catch the exit
exception raised elsewhere in a program; the script in
Example 5-15
, for instance, exits from
within a processing function.

Example 5-15. PP4E\System\Exits\testexit_sys.py

def later():
import sys
print('Bye sys world')
sys.exit(42)
print('Never reached')
if __name__ == '__main__': later()

Running this program as a script causes it to exit before the
interpreter falls off the end of the file. But because
sys.exit
raises a Python exception, importers
of its function can trap and override its exit exception or specify a
finally
cleanup block to be run
during program exit
processing:

C:\...\PP4E\System\Exits>
python testexit_sys.py
Bye sys world
C:\...\PP4E\System\Exits>
python
>>>
from testexit_sys import later
>>>
try:
...
later()
...
except SystemExit:
...
print('Ignored...')
...
Bye sys world
Ignored...
>>>
try:
...
later()
...
finally:
...
print('Cleanup')
...
Bye sys world
Cleanup
C:\...\PP4E\System\Exits> # interactive session process exits
os Module Exits

It’s possible
to exit Python in other ways, too. For instance, within a
forked child process on Unix, we typically call the
os._exit
function rather than
sys.exit
; threads may exit with a
_thread.exit
call; and tkinter GUI
applications often end by calling something named
Tk().quit()
. We’ll meet the tkinter module
later in this book; let’s take a look at
os
exits here.

On
os._exit
, the calling
process exits immediately instead of raising an exception that could be
trapped and ignored. In fact, the process also exits without flushing
output stream buffers or running cleanup handlers (defined by the
atexit
standard library module), so
this generally should be used only by child processes after a fork,
where overall program shutdown actions aren’t desired.
Example 5-16
illustrates the
basics.

Example 5-16. PP4E\System\Exits\testexit_os.py

def outahere():
import os
print('Bye os world')
os._exit(99)
print('Never reached')
if __name__ == '__main__': outahere()

Unlike
sys.exit
,
os._exit
is immune to both
try
/
except
and
try
/
finally
interception:

C:\...\PP4E\System\Exits>
python testexit_os.py
Bye os world
C:\...\PP4E\System\Exits>
python
>>>
from testexit_os import outahere
>>>
try:
...
outahere()
...
except:
...
print('Ignored')
...
Bye os world # exits interactive process
C:\...\PP4E\System\Exits>
python
>>>
from testexit_os import outahere
>>>
try:
...
outahere()
...
finally:
...
print('Cleanup')
...
Bye os world # ditto
Shell Command Exit Status Codes

Both the
sys
and
os
exit calls we just met accept an argument that denotes the
exit status code of the process (it’s optional in the
sys
call but required by
os
). After exit, this code may be interrogated
in shells and by programs that ran the script as a child process. On
Linux, for example, we ask for the
status
shell variable’s value in order to
fetch the last program’s exit status; by convention, a nonzero status
generally indicates that some sort of problem occurred:

[[email protected]]$
python testexit_sys.py
Bye sys world
[[email protected]]$
echo $status
42
[[email protected]]$
python testexit_os.py
Bye os world
[[email protected]]$
echo $status
99

In a chain of command-line programs, exit statuses could be
checked along the way as a simple form of cross-program
communication.

We can also grab hold of the exit status of a program run by
another script. For instance, as introduced in Chapters
2
and
3
,
when launching shell commands, exit status is provided as:

  • The return value of an
    os.system
    call

  • The return value of the
    close
    method of an
    os.popen
    object (for historical reasons,
    None
    is returned if the exit
    status was
    0
    , which means no
    error occurred)

  • A variety of interfaces in the
    subprocess
    module (e.g., the
    call
    function’s return value, a
    Popen
    object’s
    returnvalue
    attribute and
    wait
    method result)

In addition, when running programs by forking processes, the exit
status is available through the
os.wait
and
os.waitpid
calls in a parent process.

Exit status with os.system and os.popen

Let’s look at the case of the
shell commands first—the following, run on Linux, spawns
Example 5-15
, and
Example 5-16
reads the output streams
through pipes and fetches their exit status codes:

[[email protected]]$
python
>>>
import os
>>>
pipe = os.popen('python testexit_sys.py')
>>>
pipe.read()
'Bye sys world\012'
>>>
stat = pipe.close()
# returns exit code
>>>
stat
10752
>>>
hex(stat)
'0x2a00'
>>>
stat >> 8
# extract status from bitmask on Unix-likes
42
>>>
pipe = os.popen('python testexit_os.py')
>>>
stat = pipe.close()
>>>
stat, stat >> 8
(25344, 99)

This code works the same under Cygwin Python on Windows. When
using
os.popen
on such Unix-like
platforms, for reasons we won’t go into here, the exit status is
actually packed into specific bit positions of the return value; it’s
really there, but we need to shift the result right by eight bits to
see it. Commands run with
os.system
send their statuses back directly through the Python library
call:

>>>
stat = os.system('python testexit_sys.py')
Bye sys world
>>>
stat, stat >> 8
(10752, 42)
>>>
stat = os.system('python testexit_os.py')
Bye os world
>>>
stat, stat >> 8
(25344, 99)

All of this code works under the standard version of Python for
Windows, too, though exit status is not encoded in a bit mask (test
sys.platform
if your code must
handle both formats):

C:\...\PP4E\System\Exits>
python
>>>
os.system('python testexit_sys.py')
Bye sys world
42
>>>
os.system('python testexit_os.py')
Bye os world
99
>>>
pipe = os.popen('python testexit_sys.py')
>>>
pipe.read()
'Bye sys world\n'
>>>
pipe.close()
42
>>>
>>>
os.popen('python testexit_os.py').close()
99
Output stream buffering: A first look

Notice that the last
test in the preceding code didn’t attempt to read the
command’s output pipe. If we do, we may have to run the target script
in
unbuffered
mode with the
-u
Python command-line
flag or change the script to flush its output manually with
sys.stdout.flush
. Otherwise, the text
printed to the standard output stream might not be flushed from its
buffer when
os._exit
is called in
this case for immediate shutdown. By default, standard output is fully
buffered when connected to a pipe like this; it’s only line-buffered
when connected to a terminal:

>>>
pipe = os.popen('python testexit_os.py')
>>>
pipe.read()
# streams not flushed on exit
''
>>>
pipe = os.popen('python -u testexit_os.py')
# force unbuffered streams
>>>
pipe.read()
'Bye os world\n'

Confusingly, you can pass mode and buffering argument to specify
line buffering in both
os.popen
and
subprocess.Popen
, but this won’t
help here—arguments passed to these tools pertain to the calling
process’s input end of the pipe,
not
to the
spawned program’s output stream:

>>>
pipe = os.popen('python testexit_os.py', 'r', 1)
# line buffered only
>>>
pipe.read()
# but my pipe, not program's!
''
>>>
from subprocess import Popen, PIPE
>>>
pipe = Popen('python testexit_os.py', bufsize=1, stdout=PIPE)
# for my pipe
>>>
pipe.stdout.read()
# doesn't help
b''

Really, buffering mode arguments in these tools pertain to
output the caller writes to a command’s standard input stream, not to
output read from that command.

If required, the spawned script itself can also manually flush
its output buffers periodically or before forced exits. More on
buffering when we discuss the potential for
deadlocks
later in this chapter, and again in
Chapters
10
and
12
where
we’ll see how it applies to sockets. Since we brought up
subprocess
, though, let’s turn to its exit
tools next.

Exit status with subprocess

The alternative
subprocess
module
offers exit status in a variety of ways, as we saw in
Chapters
2
and
3
(a
None
value in
returncode
indicates that the spawned
program has not yet terminated):

C:\...\PP4E\System\Exits>
python
>>>
from subprocess import Popen, PIPE, call
>>>
pipe = Popen('python testexit_sys.py', stdout=PIPE)
>>>
pipe.stdout.read()
b'Bye sys world\r\n'
>>>
pipe.wait()
42
>>>
call('python testexit_sys.py')
Bye sys world
42
>>>
pipe = Popen('python testexit_sys.py', stdout=PIPE)
>>>
pipe.communicate()
(b'Bye sys world\r\n', None)
>>>
pipe.returncode
42

The
subprocess module
works
the same on Unix-like platforms like Cygwin, but unlike
os.popen
, the exit status is not encoded,
and so it matches the Windows result (note that
shell=True
is needed to run this as is on
Cygwin and Unix-like platforms, as we learned in
Chapter 2
; on Windows this argument is required only
to run commands built into
the shell, like
dir
):

[C:\...\PP4E\System\Exits]$
python
>>>
from subprocess import Popen, PIPE, call
>>>
pipe = Popen('python testexit_sys.py', stdout=PIPE, shell=True)
>>>
pipe.stdout.read()
b'Bye sys world\n'
>>>
pipe.wait()
42
>>>
call('python testexit_sys.py', shell=True)
Bye sys world
42
BOOK: Programming Python
11.02Mb size Format: txt, pdf, ePub
ads

Other books

Lessons Learned by Sydney Logan
Vanished in the Night by Eileen Carr
Duplicate Keys by Jane Smiley
Pie 'n' Mash and Prefabs by Norman Jacobs
Burn My Heart by Beverley Naidoo
Hope Breaks: A New Adult Romantic Comedy by Alice Bello, Stephanie T. Lott
Internal Affair by Marie Ferrarella


readsbookonline.com Copyright 2016 - 2024