Skip to content

imandr/pythreader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PyThreader

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

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 as a Semaphore

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.

Primitive as a Lock

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.

Primitive as a Condition

Every Primitive object has methods await() and wakeup()

await(timeout = None, function=None, arguments=())

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:

  1. lock the object (acquire the lock associated with the underlying condition)
  2. call the wait() method of the condition (this will unlock the object and lock it again)
  3. if a function was specified, call it with provided parameters like this (while the object is locked!):
    value = function(*arguments)
  4. unlock the object
  5. 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.

wakeup(n=1, all=False, function=None, arguments=())

Calling object's wakeup method will:

  1. lock the obejct
  2. if a function is specified, call it with given arguments:
    function(*arguments)
  3. wake up one or many or all threads blocked by the await() call
  4. unlock the object

Decorators

PyThreader provides 2 decorator functions, named "sychronized" and "gated", which can be attributed to methods of Primitive subclasses.

synchronized

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.

gated

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.

About

A set of useful tools built on top of standard Python threading module

Resources

License

Stars

Watchers

Forks

Packages

No packages published