Multithreaded Programming in Python Nick Anastasia Background Basics
Multithreaded Programming in Python Nick Anastasia
Background Basics: • Process - a program in execution. Has its own address space, memory, stack, etc. All managed by the Operating System • Thread - a "lightweight process" that can share a process with other threads and use the same context.
Python and Threading: • Python execution is controlled by the Python Virtual Machine, the main loop of the interpreter • Only ONE thread can execute in the interpreter at a time (implications on multiprocessor systems? ) • Access controlled by the Global Interpreter Lock GIL just like cpu scheduler • New thread when running thread voluntarily sleeps, ends or when a specified number of instructions executes • External code locks GIL because interpreter has no way of counting python byte code intervals on it (because its not python)
Life Without Threads: Code example from book from time import sleep, ctime assume loop 0 and loop 1 actually do something useful and together they come up with a solution def loop 0(): print 'start loop 0 at time: ', ctime() sleep(4) print 'done loop 0 at time: ', ctime() running them in parallel would be better. def loop 1(): print 'start loop 1 at time; ', ctime() sleep(2) print 'done loop 1 at time: ', ctime() if __name__=='__main__': print 'starting program at time: ', ctime() loop 0() loop 1() print 'done program at time: ', ctime()
thread Module: • low level thread control, don't use it unless you really know what your doing • ends program when main thread ends does not wait for child threads • threading module is the way to go
Multithreaded solution: If main thread did not sleep longer than the child threads execution would terminate when the main function finishes. import thread from time import sleep, ctime def loop 0(): print 'start loop 0 at time: ', ctime() sleep(4) print 'done loop 0 at time: ', ctime() def loop 1(): print 'start loop 1 at time; ', ctime() sleep(2) print 'done loop 1 at time: ', ctime() if __name__=='__main__': print 'starting program at time: ', ctime() thread. start_new_thread(loop 0, ()) thread. start_new_thread(loop 1, ()) sleep(6) print 'done program at time: ', ctime()
thread Functions: • thread. start_new_thread(function, args, kwards=None) Spawns a new thread and executes function with the given args and optional kwargs • thread. allocate_lock() Allocates a Lock. Type object • thread. exit() exits the thread • Lock. Type Functions: • aquire(wait=None) Attempts to aquire lock • locked() returns true if lock aquired, false otherwise • release() releases lock
Using Locks instead of Sleep: import thread from time import sleep, ctime loops = [4, 2] def loop(nloop, nsec, lock): print 'start loop ', nloop, ' at time: ', ctime() sleep(nsec) print 'done loop ', nloop, ' at time: ', ctime() lock. release() 2. execute each thread and then release its lock if __name__=='__main__': print 'starting program at time: ', ctime() locks = [] nloops = range(len(loops)) for l in nloops: lock = thread. allocate_lock() lock. acquire() locks. append(lock) 1. acquire a lock for each thread for l in nloops: thread. start_new_thread(loop, (l, loops[l], locks[l])) for l in nloops: while locks[l]. locked(): pass print 'done program at time: ', ctime() 3. wait for all locks to be released then finish program
Threading module objects: • Thread Object that represents a single thread of execution • Lock Primitive lock object Lock. Type from thread – Lock() • • Re-entrant lock object allows for a single thread to (re)acquire an already held lock – RLock() Object that causes one thread to wait until a certain “condition” has Condition been satisfied by another thread – Condition() Event General version of condition variables can awaken any number of – Event() threads when the event happens Semaphore Provides a “waiting area” for threads waiting for a lock RLock – Semaphore([value]) • Timer Similar to Thread but waits an allotted period of time before running – Timer(time, function)
Thread object Functions: • start() Beings thread execution • run() Thread functionality, usually overwritten (same as java threads) • join(timeout = None) • get. Name() Suspends main program until started threads terminate, blocks unless timeout is given Gets thread name • set. Name(name) Sets thread name • is. Alive() Flag indicating whether thread is still running • is. Daemon() Returns the daemon flag of the thread • setdaemon(daemonic) Sets the daemon flag of the thread
Lock, RLock, Condition, Semaphore, Event, and Timer • Lock Functions: – acquire() – release() • RLock Funcitons: – acquire() – release() • Condition Functions: – – – acquire() release() wait() notify. All() • Semaphore Functions: – acquire() – release() • Event Funcitons: – – is. Set() set() clear() wait() • Timer: – Timer(time, function) – Thread Functions – cancel()
threading solution: import threading from time import sleep, ctime loops = [4, 2] def loop(nloop, nsec): print 'start loop ', nloop, ' at time: ', ctime() sleep(nsec) print 'done loop ', nloop, ' at time: ', ctime() 3. Complete loops if __name__=='__main__': print 'starting program at time: ', ctime() threads = [] nloops = range(len(loops)) 1. Make Thread for l in nloops: t = threading. Thread(target=loop, args=(l, loops[l])) threads. append(t) objects from the loop function for l in nloops: threads[l]. start() 2. Start Threads for l in nloops: threads[l]. join() 4. Wait for Thread completion and finish program print 'done program at time: ', ctime()
Practical Solution: • Making a function into a thread is nice for examples but more likely you’ll want more functionality • Get it by extending the Thread class and overriding the run() function, very similar to JAVA threads
import threading from time import sleep, ctime loops = (4, 2) class My. Thread(threading. Thread): def __init__(self, func, args, name=''): threading. Thread. __init__(self) self. name=name self. func=func self. args=args def run(self): apply(self. func, self. args) My. Thread Object: 3. My. Thread object creates thread and runs the function it is passed def loop(nloop, nsec): print 'start loop ', nloop, ' at time: ', ctime() sleep(nsec) print 'done loop ', nloop, ' at time: ', ctime() if __name__=='__main__': print 'starting program at time: ', ctime() threads = [] nloops = range(len(loops)) for l in nloops: t = My. Thread(loop, (l, loops[l]), loop. __name__) threads. append(t) for l in nloops: threads[l]. start() for l in nloops: threads[l]. join() 1. Make My. Thread objects from the loop function 2. Start Threads 4. Wait for Thread completion and finish program print 'done program at time: ', ctime()
Other threading Functions: • active. Count() • current. Thread() • enumerate() • More on page 810 number of currently active Thread objects returns the current Thread object returns a list of active Threads
Application: Producer Consumer Problem • Producer creates goods and places them in a Queue at random intervals • Consumer consumes the produced goods from the Queue • Queue used for inter-thread communication
My. Thread Class: import threading from time import sleep, ctime class My. Thread(threading. Thread): def __init__(self, func, args, name=''): threading. Thread. __init__(self) self. name=name self. func=func self. args=args def get. Result(self): return self. res def run(self): print 'start ', self. name, ' at time: ', ctime() self. res = apply(self. func, self. args) print 'finishing ', self. name, ' at time: ', ctime()
prodcon. py from My. Thread import My. Thread from time import ctime, sleep from Queue import Queue from random import randint def write. Q(queue): print 'producing object for Q. . . ', queue. put('xxx', 1) print "size now", queue. qsize() def read. Q(queue): val= queue. get(1) print 'consumed object from Q. . . size now', queue. qsize() def writer(queue, loops): for i in range(loops): write. Q(queue) sleep(randint(1, 3)) def reader(queue, loops): for i in range(loops): read. Q(queue) sleep(randint(2, 5)) funcs = [writer, reader] nfuncs = range(len(funcs)) if __name__=='__main__': nloops=randint(2, 5) q=Queue(32) threads=[] for i in nfuncs: t=My. Thread(funcs[i], (q, nloops), funcs[i]. __name__) threads. append(t) for i in nfuncs: threads[i]. start() for i in nfuncs: threads[i]. join() print 'all DONE'
mutex Module • Provides the Mutex object • A lock and queue of functions waiting to obtain the lock • Functions: – test() check if locked – testandset() aquire lock if unlocked return true, false otherwise – lock(function, argument) put function and argument in the queue to be executed when the lock is available – unlock() unlock if the queue is empty, execute the next item in the queue if it is not empty
• Questions?
- Slides: 20