Emperor's New Coroutines?
Marko Rauhamaa
marko at pacujo.net
Thu Jul 10 13:56:09 EDT 2014
Marko Rauhamaa <marko at pacujo.net>:
> The asyncio module comes with coroutine support. Investigating the
> topic on the net reveals that FSM's are for old people and the brave
> new world uses coroutines. Unfortunately, all examples I could find
> seem to be overly simplistic, and I'm left thinking coroutines have
> few practical uses in network programming.
>
> [...]
>
> but how would I modify the philosopher code to support such master
> resets?
Ok. I think I have found the answer to my question:
asyncio.wait(coroutines, return_when=asyncio.FIRST_COMPLETED)
That facility makes it possible to multiplex between several stimuli.
The code below implements a modified dining philosophers protocol. The
philosophers are accompanied by an assistant who occasionally prods the
philosophers to immediately resume thinking, thus breaking the deadlock.
Whether the coroutine style is easier on the eye than, say, callbacks
and state machines is a matter of personal opinion.
Marko
===clip-clip-clip=======================================================
#!/usr/bin/env python3
import os, sys, asyncio, random, enum, time
T0 = time.time()
def main():
loop = asyncio.get_event_loop()
try:
fork1 = Fork()
fork2 = Fork()
fork3 = Fork()
nag = Nag()
loop.run_until_complete(asyncio.wait([
Philosopher("Plato", fork1, fork2, nag).philosophize(),
Philosopher("Nietsche", fork2, fork3, nag).philosophize(),
Philosopher("Hintikka", fork3, fork1, nag).philosophize(),
assistant(nag) ]))
finally:
loop.close()
class Philosopher:
def __init__(self, name, left, right, nag):
self.name = name
self.left = left
self.right = right
self.nag = nag
@asyncio.coroutine
def philosophize(self):
yield from self.nag.acquire()
try:
while True:
self.nag_count = self.nag.count
pending = yield from self.think()
if pending is None:
continue
pending = yield from self.grab_fork("left", self.left, pending)
if pending is None:
continue
try:
pending = yield from self.wonder_absentmindedly(pending)
if pending is None:
continue
pending = yield from self.grab_fork(
"right", self.right, pending)
if pending is None:
continue
try:
pending = yield from self.dine(pending)
if pending is None:
continue
finally:
self.say("put back right fork")
self.right.release()
pending = yield from self.wonder_absentmindedly(pending)
if pending is None:
continue
finally:
self.say("put back left fork")
self.left.release()
finally:
self.nag.release()
def say(self, message):
report("{} {}".format(self.name, message))
def nagged(self):
return self.nag.count > self.nag_count
@asyncio.coroutine
def think(self):
self.say("thinking")
result, pending = yield from multiplex(
identify(Impulse.TIME, random_delay()),
identify(Impulse.NAG, self.nag.wait_for(self.nagged)))
if result is Impulse.NAG:
self.say("nagged")
return None
assert result is Impulse.TIME
self.say("hungry")
return pending
@asyncio.coroutine
def grab_fork(self, which, fork, pending):
self.say("grabbing {} fork".format(which))
result, pending = yield from multiplex(
identify(Impulse.FORK, fork.acquire()),
*pending)
if result is Impulse.NAG:
self.say("has been nagged")
return None
assert result is Impulse.FORK
self.say("got {} fork".format(which))
return pending
@asyncio.coroutine
def wonder_absentmindedly(self, pending):
self.say("now, what was I doing?")
result, pending = yield from multiplex(
identify(Impulse.TIME, random_delay()),
*pending)
if result is Impulse.NAG:
self.say("nagged")
return None
assert result is Impulse.TIME
self.say("oh, that's right!")
return pending
@asyncio.coroutine
def dine(self, pending):
self.say("eating")
result, pending = yield from multiplex(
identify(Impulse.TIME, random_delay()),
*pending)
if result is Impulse.NAG:
self.say("nagged")
return None
assert result is Impulse.TIME
self.say("that hit the spot!")
return pending
@asyncio.coroutine
def assistant(nag):
n = 1
while True:
report("assistant sleep {}".format(n))
yield from asyncio.sleep(n)
report("assistant nag")
yield from nag.nag()
n += 1
def report(info):
sys.stdout.write("{:9.3f} {}\n".format(time.time() - T0, info))
class Impulse(enum.Enum):
TIME = 1
NAG = 2
FORK = 3
class Fork(asyncio.Lock):
pass
class Nag(asyncio.Condition):
count = 0
@asyncio.coroutine
def nag(self):
yield from self.acquire()
try:
self.count += 1
self.notify_all()
finally:
self.release()
@asyncio.coroutine
def random_delay():
yield from asyncio.sleep(random.randint(1, 100) / 10)
@asyncio.coroutine
def identify(result, coroutine):
yield from coroutine
return result
@asyncio.coroutine
def multiplex(*coroutines):
done, pending = yield from asyncio.wait(
coroutines, return_when=asyncio.FIRST_COMPLETED)
return done.pop().result(), pending
if __name__ == '__main__':
main()
===clip-clip-clip=======================================================
More information about the Python-list
mailing list