Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #74091 > unrolled thread
| Started by | Marko Rauhamaa <marko@pacujo.net> |
|---|---|
| First post | 2014-07-07 12:42 +0300 |
| Last post | 2014-07-10 20:56 +0300 |
| Articles | 2 — 1 participant |
Back to article view | Back to comp.lang.python
Emperor's New Coroutines? Marko Rauhamaa <marko@pacujo.net> - 2014-07-07 12:42 +0300
Re: Emperor's New Coroutines? Marko Rauhamaa <marko@pacujo.net> - 2014-07-10 20:56 +0300
| From | Marko Rauhamaa <marko@pacujo.net> |
|---|---|
| Date | 2014-07-07 12:42 +0300 |
| Subject | Emperor's New Coroutines? |
| Message-ID | <87lhs5h5dn.fsf@elektro.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.
PEP-380 refers to the classic Dining Philosophers: <URL: http://www.cos
c.canterbury.ac.nz/greg.ewing/python/yield-from/yf_current/Examples/Sch
eduler/philosophers.py> (reproduced below).
Suppose I want to fix the protocol by supplying an "attendant" object
that periodically tells the philosophers to drop whatever they are doing
and start thinking. So I would add:
schedule(attendant(..., [ plato, socrates, euclid ]))
but how would I modify the philosopher code to support such master
resets?
Marko
========================================================================
from scheduler import *
class Utensil:
def __init__(self, id):
self.id = id
self.available = True
self.queue = []
def acquire(self):
#print "--- acquire", self.id, "avail", self.available
if not self.available:
block(self.queue)
yield
#print "--- acquired", self.id
self.available = False
def release(self):
#print "--- release", self.id
self.available = True
unblock(self.queue)
def philosopher(name, lifetime, think_time, eat_time, left_fork,
right_fork):
for i in range(lifetime):
for j in range(think_time):
print(name, "thinking")
yield
print(name, "waiting for fork", left_fork.id)
yield from left_fork.acquire()
print(name, "acquired fork", left_fork.id)
print(name, "waiting for fork", right_fork.id)
yield from right_fork.acquire()
print(name, "acquired fork", right_fork.id)
for j in range(eat_time):
# They're Python philosophers, so they eat spam rather than
spaghetti
print(name, "eating spam")
yield
print(name, "releasing forks", left_fork.id, "and", right_fork.id)
left_fork.release()
right_fork.release()
print(name, "leaving the table")
forks = [Utensil(i) for i in range(3)]
schedule(philosopher("Plato", 7, 2, 3, forks[0], forks[1]), "Plato")
schedule(philosopher("Socrates", 8, 3, 1, forks[1], forks[2]),
"Socrates")
schedule(philosopher("Euclid", 5, 1, 4, forks[2], forks[0]), "Euclid")
run()
[toc] | [next] | [standalone]
| From | Marko Rauhamaa <marko@pacujo.net> |
|---|---|
| Date | 2014-07-10 20:56 +0300 |
| Message-ID | <877g3l85dy.fsf@elektro.pacujo.net> |
| In reply to | #74091 |
Marko Rauhamaa <marko@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=======================================================
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web