Concurrency support?
Graydon Hoare
graydon at mozilla.com
Mon Jun 26 10:49:40 PDT 2006
John Cowan wrote:
> I believe that the facilities of PEP 342, while necessary, is
> insufficient, as it does not allow subroutines invoked by a coroutine
> to yield for it, where some of the subroutines on the dynamic chain are
> coroutine-blind (or if it does, it's too subtle for me to see how).
I've stared at PEP 342 for an hour now and cannot exactly tell.
It clearly points out this problem in the second paragraph of its
"motivation" section:
Also, generators cannot yield control while other functions are
executing, unless those functions are themselves expressed as
generators, and the outer generator is written to yield in response
to values yielded by the inner generator.
I *think* the proposed solution is in the 3rd paragraph:
a simple co-routine scheduler or "trampoline function" would
let coroutines "call" each other without blocking
But I'm having a hard time picturing the meaning of that, and how it
addresses the problem. I think it means that the problem is not going to
be addressed directly, but indirectly. Let's work through an example,
say a network server:
def http_service_loop():
while true:
s = socket.accept()
http_serve_connection(s)
def http_serve_connection(s):
req = http_read_requests(s)
f = filesystem.load_file(req.filename)
s.write(f.data())
def http_read_requests(s):
buf = s.readline()
...
Suppose we want this to yield any time it does something that might
block on i/o, so inside the OS-level accept, read, load, and write
methods. How does PEP 342 recommend we rewrite this?
I *think* it says that you must still structure all the functions
containing generators *as* generators, but that the yields you sprinkle
all over the intermediate calls can have yield-expression results fed
back into them by an outer "trampoline" function. So I think it says we
rewrite as such:
def http_service_loop():
while true:
s = yield socket.accept()
yield http_serve_connection(s)
def http_serve_connection(s):
req = yield http_read_requests(s)
f = yield filesystem.load_file(req.filename)
yield s.write(f.data())
def http_read_requests(s):
buf = yield s.readline()
...
Or something; I surely am getting the notation they have in mind wrong.
But I think the idea is that there's to be an outer function that does
something like this:
def trampoline():
x = http_service_loop()
try
y = x.send(None)
while true:
do_some_other_work_multiplexed_with_the_server_io()
y = x.send(y)
catch StopIteration:
pass
stepping the coroutine through its work by acting as a sort of auxiliary
return slot. And this would let you -- with some more code -- similarly
multiplex N service loops together, keeping track of the next value to
feed back into each as it's re-scheduled (putting aside the issue of a
call to sleep-until-one-of-these-io-channels-has-an-event).
If this is what PEP 342 is proposing, then I must admit the lua strategy
seems much more appealing: make any "yield" expression return control to
the nearest dynamic "resume". That would let the low level i/o functions
know about yield points, and all the logic inbetween the scheduler and
the i/o functions ignore them.
So, follow-on question: what's *wrong* with the lua strategy? Moreover,
why did the python strategy turn out this way? Did the python group just
not understand the better strategy? Were they concerned about the
restriction of being unable to yield through C stack frames? That seems
unlikely since the same restriction probably applies to PEP 324 yields.
Maybe they were bound by semi-compatibility with the existing (and even
weaker) iterator/generator scheme in earlier python versions?
-graydon
More information about the Es4-discuss
mailing list