Java thread support in Jython
The Java runtime makes extensive use of threads, which it uses to handle GUI events, to perform asynchronous I/O, to implement asynchronous processing, and so on.
It's easy to create Java threads in Jython: just create
instances of java.lang.Thread and subclasses of
java.lang.Runnable. For an example, see The GUI: fgui.py. You can also create
threads out of Jython functions by using the thread
module and functions of the following form:
start_new_thread(function, args) -- and -- exit() |
The start_new_thread function runs the
function argument in a new Java thread, passing the
args tuple value to the function. The exit
function can be used in the thread to end it (generally as the target
of an if statement).
When developing multithreaded programs using Java or Jython
threads, it is sometimes necessary to create synchronized functions
(or methods). Synchronized functions are functions that can
only be called from one thread at a time; meaning that other
threads are prevented from entering the function until the first
thread exits. Jython provides the synchronized module and
two functions to create synchronized functions. The functions are of the
following form:
make_synchronized(function)
-- and --
apply_synchronized(syncobj, function, pargs {, kwargs})
|
The make_synchronized function permanently synchronizes
the function argument. The apply_synchronized
function temporarily synchronizes on syncobj and then calls
the function argument.
Example: Using make_synchronized
Using make_synchronized to signal events is quite
straightforward, as shown below:
from synchronize import *
from java import lang
# define synchronization helpers
def waitForSignal (monitor):
""" Wait until the monitor is signaled. """
lang.Object.wait(monitor)
# replace with synchronized version; syncs on 1st argument
waitForSignal = make_synchronized(waitForSignal)
def notifySignal (monitor):
""" Signal monitor. """
lang.Object.notifyAll(monitor)
# replace with synchronized version; syncs on 1st argument
notifySignal = make_synchronized(notifySignal)
class Gui: # GUI support
:
def doExit (self):
self.visible = 0
notifySignal(self)
if __name__ == "__main__": # main code
:
gui = Gui()
:
print "Waiting until GUI exit requested..."
waitForSignal(gui)
print "Done"
|
Here's an example of the use of Jython threads. The example shows a set of producer and consumer threads sharing access to a common buffer. We'll start with the definition of the shared buffer, as shown below.
""" A Jython Thread Example. """
from java import lang
from synchronize import *
from thread import start_new_thread
from sys import stdout
def __waitForSignal (monitor):
apply_synchronized(monitor, lang.Object.wait, (monitor,))
def __signal (monitor):
apply_synchronized(monitor, lang.Object.notifyAll, (monitor,))
def __xprint (stream, msg):
print >>stream, msg
def xprint (msg, stream=stdout):
""" Synchronized print. """
apply_synchronized(stream, __xprint, (stream, msg))
class Buffer:
""" A thread-safe buffer. """
def __init__ (self, limit=-1):
self.__limit = limit # the max size of the buffer
self.__data = []
self.__added = () # used to signal data added
self.__removed = () # used to signal data removed
def __str__ (self):
return "Buffer(%s,%i)" % (self.__data, self.__limit)
def __len__ (self):
return len(self.__data)
def add (self, item):
""" Add an item. Wait if full. """
if self.__limit >= 0:
while len(self.__data) > self.__limit:
__waitForSignal(self.__removed)
self.__data.append(item);
xprint("Added: %s" % item)
__signal(self.__added)
def __get (self):
item = self.__data.pop(0)
__signal(self.__removed)
return item
def get (self, wait=1):
""" Remove an item. Wait if empty. """
item = None
if wait:
while len(self.__data) == 0:
__waitForSignal(self.__added)
item = self.__get()
else:
if len(self.__data) > 0: item = self.__get()
xprint("Removed: %s" % item)
return item
get = make_synchronized(get)
|
Producer and consumer definitions
The next step in the example is to take a look at the producer and consumer definitions, shown here:
class Producer:
def __init__ (self, name, buffer):
self.__name = name
self.__buffer = buffer
def __add (self, item):
self.__buffer.add(item)
def __produce (self, *args):
for item in args:
self.__add(item)
def produce (self, items):
start_new_thread(self.__produce, tuple(items))
class Consumer:
def __init__ (self, name, buffer):
self.__name = name
self.__buffer = buffer
def __remove (self):
item = self.__buffer.get()
return item
def __consume (self, count):
for i in range(count):
self.__remove()
def consume (self, count=1):
start_new_thread(self.__consume, (count,))
|
An trial run of the threading example
And finally, here's a trial run of the example code:
# all producers and consumer share this one
buf = Buffer(5)
p1 = Producer("P1", buf)
p2 = Producer("P2", buf)
p3 = Producer("P3", buf)
p4 = Producer("P4", buf)
c1 = Consumer("C1", buf)
c2 = Consumer("C2", buf)
# create 6 items
p1.produce(["P1 Message " + str(i) for i in range(3)])
p2.produce(["P2 Message " + str(i) for i in range(3)])
# consume 20 items
for i in range(5):
c1.consume(2)
c2.consume(2)
# create 20 more items
p3.produce(["P3 Message " + str(i) for i in range(10)])
p4.produce(["P4 Message " + str(i) for i in range(10)])
# consume 4 items
c1.consume(2)
c2.consume(2)
# let other threads run
lang.Thread.currentThread().sleep(5000)
xprint("Buffer has %i item(s)left" % len(buf))
|
The producer consumer example produces the following results (wrapped to two columns to save space):
Added: P1 Message 0 Added: P3 Message 7 Added: P1 Message 1 Removed: P3 Message 7 Added: P1 Message 2 Added: P3 Message 8 Added: P2 Message 0 Removed: P3 Message 8 Added: P2 Message 1 Added: P3 Message 9 Added: P2 Message 2 Removed: P3 Message 9 Removed: P1 Message 0 Added: P4 Message 0 Removed: P1 Message 1 Removed: P4 Message 0 Removed: P1 Message 2 Added: P4 Message 1 Removed: P2 Message 0 Removed: P4 Message 1 Removed: P2 Message 1 Added: P4 Message 2 Removed: P2 Message 2 Removed: P4 Message 2 Added: P3 Message 0 Added: P4 Message 3 Removed: P3 Message 0 Removed: P4 Message 3 Added: P3 Message 1 Added: P4 Message 4 Removed: P3 Message 1 Added: P4 Message 5 Added: P3 Message 2 Added: P4 Message 6 Removed: P3 Message 2 Added: P4 Message 7 Added: P3 Message 3 Added: P4 Message 8 Removed: P3 Message 3 Added: P4 Message 9 Added: P3 Message 4 Removed: P4 Message 4 Removed: P3 Message 4 Removed: P4 Message 5 Added: P3 Message 5 Removed: P4 Message 6 Removed: P3 Message 5 Removed: P4 Message 7 Added: P3 Message 6 Buffer has 2 item(s)left Removed: P3 Message 6 |



