asyncoro : Python Framework for Asynchronous, Concurrent, Distributed, Network Programming

asyncoro

asyncoro is a Python framework for concurrent and/or distributed and/or network and/or asynchronous programming using coroutines. asyncoro features include

Programs developed with asyncoro have same logic and structure as programs with threads, except for a few syntactic changes - mostly using yield with asynchronous completions that give control to asyncoro's scheduler, which interleaves executions of coroutines, similar to the way an operating system executes multiple processes.

Unlike threads, creating processes (coroutines) with asyncoro is very efficient. For reference purposes, asyncoro with Python 2.7 on Ubuntu Linux 12.04 running the concurrent program

import asyncoro, resource, time
def coro_proc(coro=None):
    yield coro.suspend()

coros = [asyncoro.Coro(coro_proc) for i in xrange(100000)]
time.sleep(5)
ru = resource.getrusage(resource.RUSAGE_SELF)
print('Max RSS: %.1f MB' % (ru.ru_maxrss / 1024.0))
for coro in coros:
    coro.resume()
shows that 100,000 coroutines take about 207 MB of resident memory (RSS field). Moreover, with asyncoro context switch occurs only when coroutines use yield (typically with an asychronous call), so there is no need for locking and there is no overhead of unnecessary context switches.

Download

asyncoro can be downloaded from Sourceforge Files. It is released under MIT license, a common license used for Python modules. 'asyncoro.py' is to be used with Python 2.7+ and 'asyncoro3.py' is to be used with Python 3.1+. If using Python 3.1+ exclusively, 'asyncoro3.py' may be renamed to 'asyncoro.py'.

Details

Traditional methods of threads and synchronous sockets have some drawbacks, especially with Python due to global interpreter lock (GIL). This approach is not suited for large number of concurrent connections, known as C10K problem. In addition, threads in Python have both memory and time overheads, especially on multi-core systems (see Inside the Python GIL).

Many asynchronous frameworks are now available to deal with this problem. Unlike with these frameworks, using asyncoro framework is very similar to the thread based programming so that there is almost no learning curve - existing thread implementations can be converted to asyncoro almost mechanically (although it cannot be automated). In fact, it may be easier to use asyncoro than threads, as locking is not required with asyncoro. Moreover, asyncoro implementation is very small (about 3000 lines), so it is easy to understand how it works and how to program with it. While not required to program with asyncoro, A Curious Course on Coroutines and Concurrency offers details on generator functions and coroutines.

For example, a simple tcp server with asyncoro looks like:

def process(sock, coro=None):
    # convert sock to asynchronous socket
    sock = AsynCoroSocket(sock)
    # get (exactly) 4MB of data, for example, and let other coroutines
    # (process methods, in this case) execute in the meantime
    data = yield sock.recvall(4096*1024)
    # ...
    yield sock.sendall(reply)
    sock.close()

if __name__ == '__main__':
    host, port = '', 3456
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((host, port))
    sock.listen(128)
    while True:
        conn, addr = sock.accept()
        Coro(process, conn)

Here we mixed synchronous sockets in server loop and asynchronous sockets in the process method for illustration. The server does not do any processing, so the loop can quickly accept connections. Each request is processed in a separate coroutine. Note that I/O event loop is transparent - asyncoro's scheduler handles I/O events automatically. A coroutine should have coro=None default argument. The coroutine builder Coro (which is similar to threading.Thread) will set coro argument with the Coro instance, which can used for calling methods in Coro class. In the process method, socket I/O operations are called with yield. With these statements, the I/O operation is initiated and control goes to asyncoro's scheduler for scheduling other coroutines as well as processing I/O events. When an I/O operation is complete, the scheduler returns the results of the I/O operation back to the method. During the time it takes to complete an I/O operation, the scheduler executes other coroutines (in this case, other process coroutines).

asyncoro has been tested with Python versions 2.7 and 3.2 under Linux, OS X and Windows. Under Windows asyncoro uses IOCP only if Python for Windows Extensions (pywin32) is installed. pywin32 works with Windows 32-bit and 64-bit.

asyncoro can be used to develop concurrent programs with coroutines running on a single instance of asyncoro using Concurrent Programming, or distributed and concurrent programs with coroutines running on multiple instances either on a single node, or multpiple nodes over network using Distributed Programming.