- Asynchronous (non-blocking) sockets, for concurrent processing of (heavy) network traffic,
- Efficient polling mechanisms epoll, kqueue, /dev/poll (and poll and select if necessary), and Windows I/O Completion Ports (IOCP) for high performance and scalability,
- SSL for security,
- Asynchronous timers, including non-blocking sleep,
- Asynchronous locking primitives similar to Python threading module,
- Message passing, for (local and remote) coroutines to exchange messages one-to-one or through broadcasting channels,
- Remote execution of coroutines for distributed/parallel programming (using message passing),
- Monitoring and restarting of (local or remote) coroutines, for fault detection and fault-tolerance,
- Hot-swapping of coroutine functions, for dynamic system reconfiguration,
- Thread pools with asynchronous task completions, for executing time consuming synchronous tasks,
- Asynchronous database cursor operations (using asynchronous thread pool)
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
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.
import asyncoro, resource 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()
Downloadasyncoro can be downloaded from Sourceforge Files; latest version 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'.
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.