Asynchronous programming: the concept of Deferred

Asynchronous programming concept is that the function execution result is not available immediately, but some time in the form of some asynchronous (breaking the usual order of execution) of the call. Why can this be useful? Consider a few examples.

The first example is a network server, a web application. Usually such calculations on the processor, such applications do not perform. Most of the time (real, not CPU) is spent on I / o reading request from the client, to access the disk for data, network access to other subsystems (database, cache server, RPC, etc.), write a response to the client. During these operations I / o processor is idle, it can be loaded with processing of other requests. There are various ways to solve this problem: a separate process for each connection (Apache mpm_prefork, PostgreSQL, PHP FastCGI), a separate stream (thread) per connection or a combined version of process/thread (Apache mpm_worker, MySQL). The approach using processes or threads shifts the multiplexing processor of the processed compounds on OS, this uses relatively many resources (memory, context switching, etc.), this option is not suitable for processing a large number of simultaneous connections, but perfect for a situation when the volume of calculations is sufficiently high (e.g., DBMS). The advantages of the model of threads and processes you can add the potential use of all available processors in a multiprocessor architecture.

The alternative is to use a single-threaded model using the primitives of the asynchronous I / o provided by the OS (select, poll, etc.). The volume of resources for each new accepted connection is not as great (new socket, some structures in application memory). However, the programming is substantially complicated, since data from network sockets do some “snippets”, and one cycle of processing the data received from different connections, in different States, some of the compounds can be inbound from customers, part — outbound to external resources (DB, another server, etc.). To simplify the development uses a different concept: callback, state machines, and others. Examples of network servers using asynchronous I / o: nginx, lighttpd, HAProxy, pgBouncer, etc. in this single-threaded model there is a need for asynchronous programming. For example, we want to query in the database. From the point of view of the program execution request is a network I / o: connecting to server, sending request, waiting for a response, read response from the DB server. So if we call the function “query the database”, it return the result cannot (otherwise it would be locked), and return something that will allow subsequently to obtain the query result or possibly an error (no connection to server, incorrect request, etc.), This return value is convenient to do just Deferred.

The second example is the development of conventional desktop applications. Suppose we decided to make an analogue of Miranda (QIP, MDC, ...), that is, his messenger. The program's interface has a contact list where you can delete the contact. When the user selects this action, he expects that contact will disappear on the screen and what is it really removed from the contact list. Actually the delete operation from the server the contact list is based on network interaction with the server, while UI should not be blocked while this operation, so in any case, after the operation, you will need some asynchronous communication with result. You can use the mechanism of signals, slots, callbacks, or something else, but it is better suited Deferred: operation delete from the contact list returns a Deferred, which will come back either a positive result (all is well) or an exception (the exact error that needs to be communicated to the user): in case of an error the contact is necessary to restore the contact to the contact list.
The examples are long and hard now about what exactly is Deferred. Deferred is the heart of the framework and asynchronous network programming Twisted in Python. This is a simple and coherent concept that allows the synchronous programming in asynchronous code, not reinventing the wheel for each situation and ensuring high quality code. Deferred is simply the return result of the function when the result is unknown (has not been received, will be received in another thread, etc.) That we can do with Deferred? We can “hang” in the chain of handlers that will be called when the result is received. While Deferred can carry not only the positive progress, but exceptions generated by the function or handler, exception processing, paravicini, etc. in fact, for synchronous code is more or less a clear parallel in terms of Deferred. For the effective development Deferred be useful this programming language, like closures, lambda-functions.

Here is an example of a synchronous code and an alternative in terms of a Deferred:

the
try:
# Download HTTP some page
page = downloadPage(url)
# Print the contents of
print page
except HTTPError, e:
# An error occurred
print "An error occured: %s", e

In the asynchronous variant with Deferred he would be written as follows:

the
def printContents(contents):
"""
Callback upon successful receipt of the page
print its contents.
"""
print contents

def handleError(failure):
"""
Errback (error handler), just print the error text.
"""

# We are ready to only handle HTTPError, other exemptions
# "fall" below.
failure.trap(HTTPError)
# Print the exception itself
print "An error occured: %s", failure

# Now the function is running asynchronously and is direct
# of the result Deferred
deferred = downloadPage(url)
# Hang on the Deferred object handlers for a successful outcome
# error (callback, errback).
deferred.addCallback(printContents)
deferred.addErrback(handleError)

Usually we return a Deferred from a function that get Deferred in the process of his work is hung in a large number of handlers, the handled exception, some exceptions are returned through a Deferred (throw up). As a more complex example code in the asynchronous embodiment, for example, the atomic counter from articles about the structure of the data in memcached, here, we assume that the access memcached as a network service is using Deferred, i.e. class methods Memcache return a Deferred (which will return either the result of the operation or an error):

the
class MCCounter(MemcacheObject):
def __init__(self, mc, name):
"""
Constructor.

@param name: the name of the counter
@type name: C{str}
"""
super(MCCounter, self).__init__(mc)
self.key = 'counter' + name

def increment(self, value=1):
"""
To increase the value of the counter to the specified value.

@param value: increment
@type value: C{int}
@return: a Deferred, the result of the operation
"""
def tryAdd(failure):
# Only process KeyError, everything else is "thrown out"
# below
failure.trap(KeyError)

# Trying to create a key if it times yet
d = self.mc.add(self.key, value, 0)
# If someone else will create the key before us
# we will process
d.addErrback(tryIncr)
# Return the Deferred, it is "pasted" in the chain
# Deferred, in the context of which we are
return d

def tryIncr(failure):
# All the same function tryAdd
failure.trap(KeyError)

d = self.mc.incr(self.key, value)
d.addErrback(tryAdd)
return d

# Trying to execute the increment received Deferred
d = self.mc.incr(self.key, value)
# Processing error
d.addErrback(tryAdd)
# Return the Deferred to the caller, it might be:
# a) to know when the operation will end
# b) treating the raw of our error (e.g. disconnection)
return d

def value(self):
"""
To obtain the value of the counter.

@return: the current counter value
@rtype: C{int}

"""
def handleKeyError(failure):
# Only process KeyError
failure.trap(KeyError)

# No key — returned 0, it will be the result
# the overlying Deferred
return 0

# Try to get key value
d = self.mc.get(self.key)
# Will process the error to lack of key
d.addErrback(handleKeyError)
# Return the Deferred, up there you can hang
# its callback and get the value of the counter
return d

The above code, you can write “shorter” by combining frequently-used operations, such as:

the
return self.mc.get(self.key).addErrback(handleKeyError)

Virtually every designs of synchronous code, you can find an analogue in the concept of asynchronous with a Deferred:
the
    the
  • synchronous sequence of operators corresponds to chain a callback with an asynchronous call;
  • the
  • call one podgram with I / o from the other corresponds to the return of Deferred Deferred (Deferred branching);
  • the
  • deep nesting chain, the propagation of exceptions in the stack corresponds to the chain of functions that return each other Deferred;
  • the
  • the try..except blocks correspond to the error handlers (errback) that can “push” further exception, any exception in the callback puts the implementation in errback;
  • the
  • to “parallel” execution of asynchronous operations is DeferredList.

Threads are often used in asynchronous programs to implement computational procedures, the implementation of blocking I / o (when there is an async counterpart). All of this is easily modeled with the simple model, ‘worker’, then there's no need with proper architecture in explicit synchronization, all elegantly included in overall flow calculations using Deferred:

the
def doCalculation(a, b):
"""
In this function, carried out calculations, simultaneous input / output
not affecting the main flow.
"""

return a/b

def printResult(result):
print result

def handleDivisionByZero(failure):
failure.trap(ZeroDivisionError)

print "Ooops! Division by zero!"

deferToThread(doCalculation, 3, 2).addCallback(printResult).addCallback(
lambda _: deferToThread(doCalculation, 3, 0).addErrback(handleDivisionByZero))

In the above example, the function deferToThread defers the execution of the specified function in a separate thread and returns a Deferred that will asynchronously received result of execution of the function or the exception if they will be thrown out. First division (3/2) runs in a separate thread, then print the result on the screen, and then run another calculation (3/0), which throws an exception handled by the function handleDivisionByZero.

One article can't describe and that I would like to say about Deferred, I managed to not write a word about how they work. If you have time to be interested — read the materials below, and I promise to write more.

the

Additional content


the
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Address FIAS in the PostgreSQL environment. Part 4. EPILOGUE

PostgreSQL: Analytics for DBA

Audit Active Directory tools with Powershell releases. Part 1