PyThreader (pronounced as pie threader) is a library built on top of the standard Python threading library module. It adds certain useful classes, which are supposed to extend the collection of the primitives intorduced by the threading module and at the same time, make using them simpler.
Primitive is a class, which represents most primitive synchronization object. It combines essential functionality of threading's Lock, Condition and Semaphore object into a single class. The user is supposed to derive their own subclasses from the Primitive class to build their own classes, implementing the desired functionality.
Primitive constructior takes one optional argument, which is its gate capacity, used by the "gated" method described below. Essentially, this parameter controls the semaphore capacity of the Primitive object.
Any primitive object can work like a reentrant lock (threaidng RLock), i.e. it can be locked and releasded. A Primitive object is locked using using with statement:
from pythreader import Primitive
class MyLock(Primitive):
...
l = MyLock()
...
with l:
# do something while the object is "locked"
Wile the Primitive object is locked, no other thread can lock the object and will have to block until the thread which has locked it releases the lock.
Every Primitive object has methods await() and wakeup()
Wait until another thread calls wakeup() method of the same object of the timeout occurs.
When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds (or fractions thereof).
This is a convenient alternative to the standard Condition.wait() method provided by the threading module. Caller of the await() method does not have to acquire the lock associated with the condition explicitly. That is done inside the await() method, which works like this:
- lock the object (acquire the lock associated with the underlying condition)
- call the wait() method of the condition (this will unlock the object and lock it again)
- if a function was specified, call it with provided parameters like this (while the object is locked!):
value = function(*arguments)
- unlock the object
- return the value returned by the user supplied function, or None otherwise
Note that if the function is specified, it is called regardless whether the wait() exited by time-out or not.
Calling object's wakeup method will:
- lock the obejct
- if a function is specified, call it with given arguments:
function(*arguments)
- wake up one or many or all threads blocked by the await() call
- unlock the object
PyThreader provides 2 decorator functions, named "sychronized" and "gated", which can be attributed to methods of Primitive subclasses.
Decorator "synchronized" is very similar to "synchronized" method attribute in Java. If the Primitive subclass metod is decorated as "synchronized", it becomes a critical section in the sense that once a thread A's execution enters the decorated method "p", any other thread (B) calling this or any other "synchronized" method "q" of the same object (not class) will block before entering the method "q" until thread A exits mething "p".
For example:
from pythreader import Primitive, synchronized
class Buffer(Primitive):
def __init__(self):
Primitive.__init__(self)
self.Buf = []
@synchronized
def push(self, x):
self.Buf.append(x)
self.Buf.sort()
@synchronized
def pop(self):
item = None
if self.Buf:
item = x[0]
self.Buf = self.Buf[1:]
return item
Note that calling a synchronized method of an object is equivalent to using with statement with the object:
class A(Primitive):
def work(self):
#...
@synchronized
def work_synchronized(self):
self.work()
a = A()
a.work_synchronized() # this is iquivalent to
with a: a.work() # ..................... this
The difference is that work_synchronized forces object locking.
Decorator "gated" makes sure only certain number of threads can have this method or any other gated mentod invoked concurrently. Maximum concurrency is defined when the Primitive object is initialized by its constructor:
from pythreader import Primitive, gated
class Gate(Primitive):
def __init__(self, concurrency):
Primitive.__init__(self, concurrency)
@gated
def phase1(self, args):
# do something ...
@gated
def phase2(self, args):
# do something else ...
G = g(5)
In this example, at any time, only 5 threads can be executing G.phase1 and G.phase2 at the same time, combined. I.e. there could be 5 threads inside phase1 and 0 inside phase2, or 3 inside phase1 and 2 inside phase2.