Created
April 8, 2014 16:36
-
-
Save jimbaker/10152999 to your computer and use it in GitHub Desktop.
Greenlet emulation for Jython using threads. Development has moved to https://github.com/jythontools/artificialturf, but this shows my original thoughts. There are reports of running the JVM with 1000s or low 10000s of threads on Linux, but this emulation at least can be used as a bridge to better solutions, such as asyncio or Quasar.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import threading | |
from java.util.concurrent import ArrayBlockingQueue | |
class GreenletExit(Exception): | |
pass | |
# Consider two greenlets, which we will name alice and bob. Some code | |
# is running in the context of alice, then it calls | |
# bob.switch(...). This code has a reference to the bob greenlet, | |
# because of the user-directed and explicit scheduling in the greenlet | |
# model. But it needs to retrieve the alice context. As we usually do | |
# in such cases, we model this context with a thread local. | |
context = threading.local() | |
# FIXME arrange for a implicit main greenlet to root the tree of | |
# greenlets in the current thread! this provides the starting point | |
# Mailbox is used in this code to highlight that it's a specialized | |
# queue of length 1, used for the specific synchronization model of | |
# greenlet.switch | |
context._mailbox = ArrayBlockingQueue(1) # FIXME starting point to consider rooting | |
class greenlet(object): | |
def __init__(self, run=None, parent=None): | |
self.run = run | |
self.parent = parent | |
self._mailbox = ArrayBlockingQueue(1) | |
self._thread = threading.Thread(target=self._wrapper) | |
self._thread.daemon = True # greenlets don't block exit | |
self._thread.start() | |
def switch(self, *args, **kwargs): | |
# Using add ensures that we will quickly fail if multiple greenlets | |
# switch to the same one. Should not happen in actual greenlets, | |
# and presumably the user-directed scheduling of switch should ensure | |
# the same for this emulation | |
self._mailbox.add((args, kwargs)) | |
try: | |
result = context._mailbox.take() | |
if isinstance(result, BaseException): | |
# FIXME presumably this is what is done, | |
# need to look at unit tests since docs | |
# are underspecified here | |
raise result | |
except GreenletExit: | |
result = None # FIXME what value? | |
return result | |
@property | |
def dead(self): | |
return not self._thread.is_alive() | |
def _wrapper(self): | |
# Now that this thread is started, we need to be prepared to switched | |
# to it on a subsequent scheduling (all user directed of course) | |
context._mailbox = self._mailbox | |
args, kwargs = self._mailbox.take() | |
print "Running greenlet thread {} args={} kwargs={}".format(self._thread.name, args, kwargs) | |
result = self.run(*args, **kwargs) | |
print "Completed greenlet thread {}".format(self._thread.name) | |
# FIXME need to support switching up the parent hierarchy | |
#self.parent.switch(result) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from greenlet import greenlet | |
def test1(): | |
while True: | |
print 12 | |
gr2.switch() | |
print 34 | |
def test2(): | |
while True: | |
print 56 | |
gr1.switch() | |
print 78 | |
gr1 = greenlet(test1) | |
gr2 = greenlet(test2) | |
gr1.switch() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment