Introduction  to Asynchronous Programming

Priyank Trivedi

Backend Engineer at Zenatix

Talk coverage 

 

  1. Introducing event driven or asynchronous programming model.

  2. Contrasting it with synchronous programming models with threading and multi-processing.
  3. Asynchronous programming concepts and caveats
  4. Demo of synchronous and asynchronous programming in Python using Twisted.
  5. Further readings and examples.

Doing more than one thing at a time, sort of !

Threading, heard of it ?

  • Very common
  • Handled by OS.
  • Threads have shared memory, shared scopes.

Surprisingly good, until it isn't

  • Difficult to access shared state safely.
  • Race conditions
  • Generally hard to do it right.

Multiprocessing

  • OS Kernel takes care of it.
  • Fairly easy to write MP code in unix-like environment.
  • fork() makes life much easier
  • Fewer race condition issues - Depends on Symmetric or Asymmetric Multiprocessing.

Multiprocessing

  • Hard to send data between processes.
  • import multiprocessing... anyone ?

Asynchronous or Event Driven 

  • Like Synchronous - Has just one thread.
  • No race conditions - mostly

 

The essential aspect of asynchronus/event-driven systems - you tell x that when y occurs, call z.

 

Synchronous and Asynchronous

What's what!

Synchronous

  • Processing not allowed before current execution is done

    • e.g. blocking IO operations

Synchronous : Single Threaded

  • Task 3 will be executed after Task 2 is complete

Synchronous : Single Threaded

def sum(list_of_nums):
    sum = 0
    
    for (i=0; i<len(list_of_nums); i++)
        s += list_of_nums[i]
    
    print "Sum is", sum
  • Each task is performed one at a time

Synchronous : Multi Threaded

  • Each task is performed in a separate thread of control
  • The details of execution are handled by the OS, instruction streams run simultaneously

Synchronous : Multi Threaded

#!/usr/bin/python

import thread
import time

# Define a function for the thread
def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print "%s: %s" % ( threadName, time.ctime(time.time()) )

# Create two threads as follows
try:
   thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print "Error: unable to start thread"

while 1:
   pass
  • Creates two threads to print current system time.

Asynchronous Programming Model

Asynchronous Programming Model

  • Processing allowed before current execution is done

    • e.g. non-blocking IO operations

Key Concepts

Event Loops

Event Loop

  1. Long running while loop.
  2. When an event triggers, the loop catches the event.
  3. These events can be very generic.
    reactor.listenTCP(8080, someFactory)
    button.connect('clicked', someCallback)

Then what ?

  1. Once the event is caught, let your code run.
  2. This could be any callable python function.

Let go

Once it's inside your callable function, you have to let go

Let go

Event

Loop

 

Your code

Let go

Event

Loop

 

Your code

Need some billion rows data from MySQL

Let go

Event

Loop

 

Your code

Need some billion rows data from MySQL

And this happens!

Why Asynchronous?

  • To give users a better experiences

    • e.g.

      • No more "XXX is not responding" dialogue popped up

      • Show loading indicator

  • To utilize resources

How Asynchronous Model solves this

Here the method call wouldn't return you the values.

But

an object which will tell when your data is.

Program Flow

x = y() doesn't work anymore.

Twisted

x = y() doesn't work anymore.

Event Driven Networking Engine

Twisted

Event Loop as a Reactor

def hello():
    print 'Hello from the reactor loop!'
    print 'I\'m in an infinite loop here.'

from twisted.internet import reactor

reactor.callWhenRunning(hello)

print 'Starting the reactor.'
reactor.run()
  • 'reactor' here corresponds to the infinite while loop

Twisted

Event Loop = Reactor Pattern

class Countdown(object):

    counter = 5

    def count(self):
        if self.counter == 0:
            reactor.stop()
        else:
            print self.counter, '...'
            self.counter -= 1
            reactor.callLater(1, self.count)

from twisted.internet import reactor

reactor.callWhenRunning(Countdown().count)

print 'Start!'
reactor.run()
print 'Stop!'

Twisted

Deferred() = Object to manage callback sequence

  • A deferred is an indicator that something is going to happen "later on", as opposed to right now.
  • This comes back to the core ideal of having to Let Go

Twisted

Deferred() = Object to manage callback sequence

from twisted.internet.defer import Deferred

def got_poem(res):
    print 'Your poem is served:'
    print res

def poem_failed(err):
    print 'No poetry for you.'

d = Deferred()

# add a callback/errback pair to the chain
d.addCallbacks(got_poem, poem_failed)

# fire the chain with a normal result
d.callback('This poem is short.')

print "Finished"

Twisted

Reactor and Deferred together

from twisted.internet import reactor, defer

def getDummyData(x):
    """
    This function is a dummy which simulates a delayed result and
    returns a Deferred which will fire with that result. Don't try too
    hard to understand this.
    """
    d = defer.Deferred()
    # simulate a delayed result by asking the reactor to fire the
    # Deferred in 2 seconds time with the result x * 3
    reactor.callLater(2, d.callback, x * 3)
    return d

def printData(d):
    """
    Data handling function to be added as a callback: handles the
    data by printing the result
    """
    print d

d = getDummyData(3)
d.addCallback(printData)

# manually set up the end of the process by asking the reactor to
# stop itself in 4 seconds time
reactor.callLater(4, reactor.stop)
# start up the Twisted reactor (event loop handler) manually
reactor.run()

Twisted

Reactor and Deferred together

import sys

from twisted.internet.defer import Deferred

def got_poem(poem):
    print poem

def poem_failed(err):
    print >>sys.stderr, 'poem download failed'
    print >>sys.stderr, 'I am terribly sorry'
    print >>sys.stderr, 'try again later?'

def poem_done(_):
    from twisted.internet import reactor
    reactor.stop()

d = Deferred()

d.addCallbacks(got_poem, poem_failed)
d.addBoth(poem_done)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, 'Another short poem.')

reactor.run()

Twisted

Taking a step back

Event

Loop

 

Your code

Need some billion rows data from MySQL

Asynchronous Programming to your rescue

Twisted

Fetch Billion rows from MySQL

"""
We have expanded our row processor into its own function as
well as adding an error handler to the twisted Deferred.
"""
def gotaRequest(request, response):
    df = mySqlConn.query("SELECT * FROM Pydelhi")
    df.addCallback(_someData, response)
    df.addErrback(_someError, response)

def _someData(rows, response):
    response.render(rows)
    response.finish()

def _someError(e, response):
    response.render("Error! %s" % e)
    response.finish()

d = Deferred()

d.addCallbacks(_someData, _someError)

from twisted.internet import reactor

reactor.callWhenRunning(d.callback, request)

reactor.run()

Asynchronous Caveats

What it is and What it's not

Asynchronous != Faster

  • Event loop is an overhead.
  • In an environment where the network I/O, disk I/O is huge, asynchronous programming is a must, otherwise it's useless.
  • Unless, you structure your callbacks right, it's again useless and will not benefit.

You must be thinking

Why Bother ?

Why Bother

  1. Scales beautifully.
    1. No threads means no thread overhead, and no complexity of maintaining locks and trying to share state.
  2. Elegance.
  3. Closer reality mapping.

 The asynchronous model performs best when:

As compared to the synchronous model,

  • There are a large number of tasks so there is likely always at least one task that can make progress.

  • The tasks perform lots of I/O, causing a synchronous program to waste lots of time blocking when other tasks could be running.

  • The tasks are largely independent from one another so there is little need for inter-taskcommunication (and thus for one task to wait upon another).

Introduction to Asynchronous Programming

By priyankt68

Introduction to Asynchronous Programming

  • 720