threading

Chris Angelico rosuav at gmail.com
Thu Apr 10 05:40:57 EDT 2014


On Thu, Apr 10, 2014 at 7:17 PM, Frank Millman <frank at chagford.com> wrote:
> The current version of my program uses HTTP. As I understand it, a client
> makes a connection and submits a request. The server processes the request
> and returns a result. The connection is then closed.
>
> In this scenario, does async apply at all? There is no open connection to
> 'select' or 'poll'. You have to ensure that the request handler does not
> block the entire process, so that the main loop is ready to accept more
> connections. But passing the request to a thread for handling seems an
> effective solution.

Let's take this to a slightly lower level. HTTP is built on top of a
TCP/IP socket. The client connects (usually on port 80), and sends a
string like this:

"""GET /foo/bar/asdf.html HTTP/1.0
Host: www.spam.org
User-Agent: Mozilla/5.0

"""

The server then sends back something like this:

"""HTTP/1.0 200 OK
Content-type: text/html

<html>
<body>
Hello, world!
</body>
</html>
"""

These are carried on a straight-forward bidirectional stream socket,
so the write and read operations (or send and recv, either way) can
potentially block. With a small request, you can kinda assume that the
write won't block, but the read most definitely will: it'll block
until the server writes something for you.

So it follows the usual model of blocking vs non-blocking. In blocking
mode, you do something like this:

data = socket.read()

and it waits until it has something to return. In non-blocking mode,
you do something like this:

def data_available(socket, data):
    # whatever
socket.set_read_callback(data_available)

An HTTP handling library can then build a non-blocking request handler
on top of that, by having data_available parse out the appropriate
information, and return if it doesn't have enough content yet. So it
follows the same model; you send off the request (and don't wait for
it), and then get notified when the result is there.

When you write the server, you effectively have the same principle,
with one additional feature: a listening socket becomes readable
whenever someone connects. So you can select() on that socket, just
like you can with the others, and whenever there's a new connection,
you add it to the collection and listen for requests on all of them.
It's basically the same concept; as soon as you can accept a new
connection, you do so, and then go back to the main loop.

It's pretty simple when you let a lower-level library do the work for
you :) The neat thing is, you can put all of this into a single
program; I can't demo it in Python for you, but I have a Pike kernel
that I wrote for my last job, which can handle a variety of different
asynchronous operations: TCP, UDP (which just sends single packets,
normally), a GUI (in theory), timers, the lot. It has convenience
features for creating a DNS server, an HTTP server, and a stateful
line-based server (covers lots of other protocols, like SMTP). And
(though this bit would be hard to port to Python) it can update itself
without shutting down. Yes, it can take some getting your head around,
but it's well worth it.

ChrisA



More information about the Python-list mailing list