cse 399-004: python programmingcse39904/lectures/06-withthreads.pdf•python's with-statement...
TRANSCRIPT
CSE 399-004:Python Programming
Lecture 06: with-statements and threadsFebruary 19, 2007
http://www.seas.upenn.edu/~cse39904/
Announcements
• Projects• Feedback on proposals by next week• No need to start on these right now• Due date: Friday April 20, 2007 at 5pm
• Homework 6• Already posted• Due one week from today
2
The "with" statement(Tutorial, Section 8.7)
Working with files
4
file = open("foo.txt")data = file.read()print datafile.close()
Working with files
4
file = open("foo.txt")data = file.read()print datafile.close()
It's important to close a file when you're done with it.
Working with files
4
file = open("foo.txt")data = file.read()print datafile.close()
Suppose now that all sorts of things can go wrong here. How many places are you going to have to call file.close() now?
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
The with-statement
5
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
The with-statement
5
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data
Signals to the interpreter that this module uses with-statements, which aren't standard yet. Must be the first non-doc string code in the file.
The with-statement
6
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data
the object managed by this with-statement
The with-statement
6
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data we can use file to
refer to the file object
The with-statement
7
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data
No matter how we exit this block, file.close() will be always be called.
The with-statement
7
• Python's with-statement provides a mechanism for ensuring that "clean-up" actions always get called
• What a with-statement does for a given object depends on the exact object at hand
from __future__ import with_statement
with open("foo.txt") as file: data = file.read() print data
Adding support for with-statements
• See Section 3.4.9 of the Language Reference for information on the __enter__ and __exit__ methods
• These two methods define how with-statements work:• __enter__ defines the initialization behavior• __exit__ defines the clean-up behavior
• You don't have to understand these two methods if you only want to use with-statements
8
Threads: Overview
Operating systems and processes
• Definition: A process is a running program
• An OS manages the running of multiple processes on limited hardware resources
• One process cannot directly access the memory of another process
• Processes are forced to share CPU time
• But each process thinks it has its own CPU and vast amounts of memory (~4GB on a 32-bit machine)
10
Doing multiple things at once
• Often, a process wants to do multiple things at once
• Typical example: Web browser• One window might be loading a page• Another window might need to animate a movie• The user might be typing into a form
• It doesn't make sense for each of these tasks to be its own process — they need share state
11
Threads
• Threads allow a process to do multiple things at once
• A process can have multiple threads• Each thread has its own local state• All threads share the same global state
• Aside from the sharing of global state, a thread is conceptually the same as a process
• However, how threads are scheduled to be run is dependent on how they're implemented
12
Threads in Python
• Support for multi-threaded programs in Python is provided by the thread and threading modules
• Python threads are pre-emptive in that threads may be interrupted at arbitrary times
• But, due to how the Python interpreter is implemented, only one thread can be running at any given time
• So why use multiple threads in Python?• Input/output latency• Conceptually cleaner code
13
Caveats about Python threads
• Not all operations are thread-safe
• Some operations may not behave correctly when multiple threads are running
• Some operations may block the entire process, meaning that no thread gets to run until the operation completes
• It's difficult to program for pre-emptive threads• You never know when you might be interrupted• What happens if you're modifying global state?
14
Common problems
• Race conditions: Code may be sensitive to who gets to do something first
• Deadlock: Two threads might both get stuck waiting for the other thread to do something
• Starvation: One thread may never get to run• This can happen even with pre-emptive threads
• Simply engineer your code in just the right way…
15
Goal for today's class
• Introduce you to (some of ) Python's support for threads
• Non-goal: Make you an expert on multi-threaded programming and all its issues
• Multi-threaded programming is difficult and better discussed in a class on concurrent systems, for example
16
The threading module
The Thread class
• If you want to create your own threads, you should create a subclass of the Thread class
• Subclasses should:• Always call Thread.__init__()
• Python will complain if you don't do this• The Thread class does some required initialization
• Override the run() method• This method defines what the thread does• By default, it does nothing
• To start a thread object executing, invoke start()
18
Thread: Example
19
sum = 0
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sum += j
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()print sum
Thread: Example
19
sum = 0
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sum += j
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()print sum
Needed to ensure that the sum refers to the global version of sum. That is, sum is shared between all threads.
Possible outputs from that program
20
• Run the program multiple times, and you may see:• 311738101• 6800400028• 9999900000 ("correct")• 5317180266• 7030865778• 6795902278• 16380465• 5001560115• 106975597
• What is going on here??
Problem 1: Printing sum too early
• The "main" thread prints out sum before the other two threads have finished executing
• To see this, tweak the definition of the class
21
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sum += j print "Done".
New sample output
• Run the program a couple of times
• 3352925422Done.Done.
• 104111214Done.Done.
• The "main" thread (the one started by Python when it first starts executing your code) is indeed printing sum far too early
22
Thread.join()
• The join() method, when called on a thread object T, causes the calling thread to block until T finishes
23
sum = 0
class SumThread(threading.Thread): ...
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()th1.join()th2.join()print sum
New sample output
• Run this new version a couple of times
• Done.Done.9516678490
• Done.Done.9999900000
• Um… something is still not quite right…
24
"correct"
Problem 2: Lack of atomicity
• The line sum += j does not execute atomically• Thread has to read sum• Then compute sum + j• Then set sum to what it just computed
• The thread could be interrupted between any of those actions, and sum could be changed without it knowing
• Atomic operations, on the other hand, compute as if they were never interrupted by other threads
25
Mutual exclusion
• In order to effectively use shared state, we need a way to prevent threads from stepping all over each other
• Mutual exclusion refers to making sure that only some number of threads, often just 1, are doing something
• Some Python constructs which provide mutual exclusion:• Lock and RLock• Semaphore and BoundedSemaphore• Condition (won't discuss this today)
26
Lock
• A Lock supports two atomic operations• acquire()• release()
• Initially, a lock is in the untaken state• acquire() causes it to be taken• release() causes it to be untaken
• You can't:• release an untaken lock — this is an error• acquire a taken lock — call blocks until lock is released
• Basic idea: "take a lock" if you need exclusive access27
RLock
• A variation on Lock — the "R" stands for "reentrant"
• If a thread has already called acquire() on an RLock and attempts to acquire() it again, the second call succeeds
• With a plain Lock, the second call would block, thus leading to a thread deadlocked with itself
• Aside: Java's implicit locks used in implementing synchronized are reentrant
28
sum = 0sumLock = threading.Lock()
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sumLock.acquire() sum += j sumLock.release()
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()th1.join()th2.join()print sum 29
sum = 0sumLock = threading.Lock()
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sumLock.acquire() sum += j sumLock.release()
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()th1.join()th2.join()print sum 29
This is a function which returns an object. (The capitalization is misleading.)
sum = 0sumLock = threading.Lock()
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): sumLock.acquire() sum += j sumLock.release()
th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()th1.join()th2.join()print sum 29
Before we do anything with sum, acquire the corresponding lock. When we're done, release it.
New sample output
• Run this new version a couple of times
• You will always get: 9999900000
• Wasn't that fun…
30
Semaphore
• A semaphore is, conceptually, a non-negative integer which can be incremented/decremented atomically
• Semaphore(n) creates a semaphore with initial value n
• release() increments the semaphore's value
• acquire() decrements the semaphore's value
31
Semaphore (continued)
• If the value is zero when acquire() called, then the call blocks, i.e., the calling thread waits until the value becomes non-zero
• If multiple threads are blocked on a semaphore when release() is called, one of them is chosen randomly and allowed to proceed
• Use semaphores when you need to limit the number of threads than can simultaneously be doing something
32
BoundedSemaphore
• BoundedSemaphore(n) creates a semaphore with initial value n, and it's value can never exceed n
• BoundedSemaphore(1) is equivalent to a simple lock
• Probably more useful than Semaphore: extra calls to release() usually correspond to serious bugs
33
with statements
• Both semaphores and locks can be used withwith-statements in a fairly natural way
• The with-statement automatically calls acquire() before executing the block and release() upon exit the block
34
with some_lock: # do stuff
with some_semaphore: # do more stuff
sum = 0sumLock = threading.Lock()
class SumThread(threading.Thread): def __init__(self, i): threading.Thread.__init__(self) self.i = i def run(self): global sum for j in range(0, self.i): with sumLock: sum += j th1 = SumThread(100000)th2 = SumThread(100000)th1.start()th2.start()th1.join()th2.join()print sum 35
This code does the exact same thing as the previous version.
Daemon threads
• Python keeps running as long as some thread is running
• You can call setDaemon(True) on a Thread object to turn it into a "daemon" thread
• False turns it to a normal thread• Daemon status is inherited from the creating thread
• If only daemon threads are running, Python will exit
• You must call setDaemon() before calling start()
36
A final note: Documentation
• thread module: Section 15.2 in the Library Reference• threading module: Section 15.3 in the Library Reference
• Not everything in threading was presented here
• The thread module was also not presented here• It's low level, but also simpler• The threading module provides a nicer interface
37
Homework 6
The dining philosophers
• Some philosophers are sitting at a table
• Between each pair of philosophers is a single chopstick
• Each philosopher has a bowl of rice
39
The dining philosophers
• Philosophers alternate between thinking and eating
• The life of a philosopher• Thinks for a bit• Becomes hungry• Grabs chopstick on their left• Grabs chopstick on their right• Eats some rice• Puts chopsticks down• Goes back to thinking
40
The dining philosophers
• Philosophers alternate between thinking and eating
• The life of a philosopher• Thinks for a bit• Becomes hungry• Grabs chopstick on their left• Grabs chopstick on their right• Eats some rice• Puts chopsticks down• Goes back to thinking
40
they all do the same thing…
The dining philosophers
• Philosophers alternate between thinking and eating
• The life of a philosopher• Thinks for a bit• Becomes hungry• Grabs chopstick on their left• Grabs chopstick on their right• Eats some rice• Puts chopsticks down• Goes back to thinking
40
grabbing both at the same time is too complicated
The dining philosophers
• Philosophers alternate between thinking and eating
• The life of a philosopher• Thinks for a bit• Becomes hungry• Grabs chopstick on their left• Grabs chopstick on their right• Eats some rice• Puts chopsticks down• Goes back to thinking
41
Philosopher won't go back to thinking until she's eaten
42
The dining philosophers problem
• The philosophers are:• Too polite to steal chopsticks from each other• Too stubborn to put a chopstick down if they
haven't eaten yet
• Suppose every philosopher grabs their left chopstick at the same time
• What happens?
42
The dining philosophers problem
• The philosophers are:• Too polite to steal chopsticks from each other• Too stubborn to put a chopstick down if they
haven't eaten yet
• Suppose every philosopher grabs their left chopstick at the same time
• What happens?
• Deadlock! Our poor philosophers starve since they're all waiting for their right chopsticks to become free and stubbornly holding on to their left ones
43
Homework 6
• Your task on Homework 6 is to code up the dining philosophers problem
• Each philosopher will be one thread
• The chopsticks will be shared state
• You will need to ensure that at most one philosopher holds a given chopstick at any given time
44
Future lecture topics
45
• Networking
• Graphical user interfaces
• Functional programming in Python
• And others…?