Introduction to Asynchronous Programming
Priyank Trivedi
Backend Engineer at Zenatix
Talk coverage
-
Introducing event driven or asynchronous programming model.
- Contrasting it with synchronous programming models with threading and multi-processing.
- Asynchronous programming concepts and caveats
- Demo of synchronous and asynchronous programming in Python using Twisted.
- 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
- Long running while loop.
- When an event triggers, the loop catches the event.
- These events can be very generic.
reactor.listenTCP(8080, someFactory)
button.connect('clicked', someCallback)
Then what ?
- Once the event is caught, let your code run.
- 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
- Scales beautifully.
- No threads means no thread overhead, and no complexity of maintaining locks and trying to share state.
- Elegance.
- 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