File: asynchronous-programming.rst

package info (click to toggle)
python-autobahn 22.7.1%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 8,404 kB
  • sloc: python: 38,356; javascript: 2,705; makefile: 905; ansic: 371; sh: 63
file content (381 lines) | stat: -rw-r--r-- 18,579 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
.. _async_programming:

Asynchronous Programming
========================

Introduction
------------

The asynchronous programming approach
.....................................

|Ab| is written according to a programming paradigm called *asynchronous programming* (or *event driven programming*) and implemented using *non-blocking* execution - and both go hand in hand.

A very good technical introduction to these concepts can be found in `this chapter <http://krondo.com/?p=1209>`_ of an "Introduction to Asynchronous Programming and Twisted".

Here are two more presentations that introduce event-driven programming in Python

* `Alex Martelli - Don't call us, we'll call you: callback patterns and idioms <https://www.youtube.com/watch?v=LCZRJStwkKM>`_
* `Glyph Lefkowitz - So Easy You Can Even Do It in JavaScript: Event-Driven Architecture for Regular Programmers <http://www.pyvideo.org/video/1681/so-easy-you-can-even-do-it-in-javascript-event-d>`_

Another highly recommended reading is `The Reactive Manifesto <http://www.reactivemanifesto.org>`_ which describes guiding principles, motivations and connects the dots

.. epigraph::

   Non-blocking means the ability to make continuous progress in order for the application to be responsive at all times, even under failure and burst scenarios. For this all resources needed for a response—for example CPU, memory and network—must not be monopolized. As such it can enable both lower latency, higher throughput and better scalability.

   -- `The Reactive Manifesto <http://www.reactivemanifesto.org>`_

The fact that |Ab| is implemented using asynchronous programming and non-blocking execution shouldn't come as a surprise, since both `Twisted <https://twistedmatrix.com/trac/>`__ and `asyncio <https://docs.python.org/3/library/asyncio.html>`__ - the foundations upon which |ab| runs - are *asynchronous network programming frameworks*.

On the other hand, the principles of asynchronous programming are independent of Twisted and asyncio. For example, other frameworks that fall into the same category are:

* `NodeJS <http://nodejs.org/>`_
* `Boost/ASIO <http://think-async.com/>`_
* `Netty <http://netty.io/>`_
* `Tornado <http://www.tornadoweb.org/>`_
* `React <http://reactphp.org/>`_

.. tip::
   While getting accustomed to the asynchronous way of thinking takes some time and effort, the knowledge and experience acquired can be translated more or less directly to other frameworks in the asynchronous category.


Other forms of Concurrency
..........................

Asynchronous programming is not the only approach to concurrency. Other styles of concurrency include

1. `OS Threads <http://en.wikipedia.org/wiki/Thread_%28computing%29>`_
2. `Green Threads <http://en.wikipedia.org/wiki/Green_threads>`_
3. `Actors <http://en.wikipedia.org/wiki/Actor_model>`_
4. `Software Transactional Memory (STM) <http://en.wikipedia.org/wiki/Software_transactional_memory>`_

Obviously, we cannot go into much detail with all of above. But here are some pointers for further reading if you want to compare and contrast asynchronous programming with other approaches.

With the **Actor model** a system is composed of a set of *actors* which are independently running, executing sequentially and communicate strictly by message passing. There is no shared state at all. This approach is used in systems like

* `Erlang <http://www.erlang.org/>`_
* `Akka <http://akka.io/>`_
* `Rust <http://www.rust-lang.org/>`_
* `C++ Actor Framework <http://actor-framework.org/>`_

**Software Transactional Memory (STM)** applies the concept of `Optimistic Concurrency Control <http://en.wikipedia.org/wiki/Optimistic_concurrency_control>`_ from the persistent database world to (transient) program memory. Instead of lettings programs directly modify memory, all operations are first logged (inside a transaction), and then applied atomically - but only if no conflicting transaction has committed in the meantime. Hence, it's "optimistic" in that it assumes to be able to commit "normally", but needs to handle the failing at commit time.

**Green Threads** is using light-weight, run-time level threads and thread scheduling instead of OS threads. Other than that, systems are implemented similar: green threads still block, and still do share state. Python has multiple efforts in this category:

* `Eventlet <http://eventlet.net/>`_
* `Gevent <http://gevent.org/>`_
* `Stackless <http://www.stackless.com/>`_


Twisted or asyncio?
...................

Since |Ab| runs on both Twisted and asyncio, which networking framework should you use?

Even more so, as the core of Twisted and asyncio is very similar and relies on the same concepts:

+------------------+------------------+-------------------------------------------------------------+
| Twisted          | asyncio          | Description                                                 |
+------------------+------------------+-------------------------------------------------------------+
| Deferred         | Future           | abstraction of a value which isn't available yet            |
+------------------+------------------+-------------------------------------------------------------+
| Reactor          | Event Loop       | waits for and dispatches events                             |
+------------------+------------------+-------------------------------------------------------------+
| Transport        | Transport        | abstraction of a communication channel (stream or datagram) |
+------------------+------------------+-------------------------------------------------------------+
| Protocol         | Protocol         | this is where actual networking protocols are implemented   |
+------------------+------------------+-------------------------------------------------------------+
| Protocol Factory | Protocol Factory | responsible for creating protocol instances                 |
+------------------+------------------+-------------------------------------------------------------+

In fact, I'd say the biggest difference between Twisted and asyncio is ``Deferred`` vs ``Future``. Although similar on surface, their semantics are different. ``Deferred`` supports the concept of chainable callbacks (which can mutate the return values), and separate error-backs (which can cancel errors). ``Future`` has just a callback, that always gets a single argument: the Future.

Also, asyncio is opinionated towards co-routines. This means idiomatic user code for asyncio is expected to use co-routines, and not plain Futures (which are considered too low-level for application code).

But anyway, with asyncio being part of the language standard library (since Python 3.4), wouldn't you just *always* use asyncio? At least if you don't have a need to support already existing Twisted based code.

The truth is that while the *core* of Twisted and asyncio are very similar, **Twisted has a much broader scope: Twisted is "batteries included" for network programming.**

So you get *tons* of actual network protocols already out-of-the-box - in production quality implementations!

asyncio does not include any actual application layer network protocols like HTTP. If you need those, you'll have to look for asyncio implementations *outside* the standard library. For example, `here <https://github.com/KeepSafe/aiohttp>`__ is a HTTP server and client library for asyncio.

Over time, an ecosystem of protocols will likely emerge around asyncio also. But right now, Twisted has a big advantage here.

If you want to read more on this, Glyph (Twisted original author) has a nice blog post `here <https://glyph.twistedmatrix.com/2014/05/the-report-of-our-death.html>`__.


Resources
---------

Below we are listing a couple of resources on the Web for Twisted and asyncio that you may find useful.


Twisted Resources
.................

We cannot give an introduction to asynchronous programming with Twisted here. And there is no need to, since there is lots of great stuff on the Web. In particular we'd like to recommend the following resources.

If you have limited time and nevertheless want to have an in-depth view of Twisted, Jessica McKellar has a great presentation recording with `Architecting an event-driven networking engine: Twisted Python <https://www.youtube.com/watch?v=3R4gP6Egh5M>`_. That's 45 minutes. Highly recommended.

If you really want to get it, Dave Peticolas has written an awesome `Introduction to Asynchronous Programming and Twisted <http://krondo.com/?page_id=1327>`_. This is a detailed, hands-on tutorial with lots of code examples that will take some time to work through - but you actually *learn* how to program with Twisted.

Then of course there is

* `The Twisted Documentation <https://twisted.readthedocs.org/>`_
* `The Twisted API Reference <https://twistedmatrix.com/documents/current/api/>`_

and lots and lots of awesome `Twisted talks <http://www.pyvideo.org/search?models=videos.video&q=twisted>`__ on PyVideo.


Asyncio Resources
.................

asyncio is very new (August 2014). So the amount of material on the Web is still limited. Here are some resources you may find useful:

* `Guido van Rossum's Keynote at PyCon US 2013 <http://pyvideo.org/video/1667/keynote-1>`_
* `Tulip: Async I/O for Python 3 <http://www.youtube.com/watch?v=1coLC-MUCJc>`_
* `Python 3.4 docs - asyncio <http://docs.python.org/3.4/library/asyncio.html>`_
* `PEP-3156 - Asynchronous IO Support Rebooted <http://www.python.org/dev/peps/pep-3156/>`_
* `OSB 2015 - How Do Python Coroutines Work? - A. Jesse Jiryu Davis <http://www.youtube.com/watch?v=GSk0tIjDT10>`_

However, we quickly introduce core asynchronous programming primitives provided by `Twisted <https://twistedmatrix.com/>`__ and `asyncio <https://docs.python.org/3.4/library/asyncio.html>`__:


Asynchronous Programming Primitives
-----------------------------------

In this section, we have a quick look at some of the asynchronous programming primitive provided by Twisted and asyncio to show similarities and differences.


Twisted Deferreds and inlineCallbacks
.....................................

Documentation pointers:

* `Introduction to Deferreds <https://twisted.readthedocs.org/en/latest/core/howto/defer-intro.html>`__
* `Deferreds Reference <https://twisted.readthedocs.org/en/latest/core/howto/defer.html>`__
* `Twisted inlineCallbacks <http://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#inlineCallbacks>`__

Programming with Twisted Deferreds involves attaching *callbacks* to Deferreds which get called when the Deferred finally either resolves successfully or fails with an error

.. code-block:: python

   d = some_function() # returns a Twisted Deferred ..

   def on_success(res):
      print("result: {}".format(res))

   def on_error(err):
      print("error: {}".format(err))

   d.addCallbacks(on_success, on_error)


Using Deferreds offers the greatest flexibility since you are able to pass around Deferreds freely and can run code concurrently.

However, using plain Deferreds comes at a price: code in this style looks very different from synchronous/blocking code and the code can become hard to follow.

Now, `Twisted inlineCallbacks <http://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#inlineCallbacks>`__ let you write code in a sequential looking manner that nevertheless executes asynchronously and non-blocking under the hood.

So converting above snipped to ``inlineCallbacks`` the code will look like

.. code-block:: python

   try:
      res = yield some_function()
      print("result: {}".format(res))
   except Exception as err:
      print("error: {}".format(err))

As you can see, this code looks very similar to regular synchronous/blocking Python code. The only difference (on surface) is the use of ``yield`` when calling a function that runs asynchronously. Otherwise, you process success result values and exceptions exactly as with regular code.

.. note::
   We'll only show basic usage here - for a more basic and complete introduction, please have a look at `this chapter <http://krondo.com/?p=2441>`__ from `this tutorial <http://krondo.com/?page_id=1327>`__.

--------

**Example**

The following demonstrates basic usage of ``inlineCallbacks`` in a complete example you can run.

First, consider this program using Deferreds. We simulate calling a slow function by sleeping (without blocking) inside the function ``slow_square``

.. code-block:: python
   :linenos:
   :emphasize-lines: 5,7,8,10,11

   from twisted.internet import reactor
   from twisted.internet.defer import Deferred

   def slow_square(x):
      d = Deferred()

      def resolve():
         d.callback(x * x)

      reactor.callLater(1, resolve)
      return d

   def test():
      d = slow_square(3)

      def on_success(res):
         print(res)
         reactor.stop()

      d.addCallback(on_success)

   test()
   reactor.run()

This is just regular Twisted code - nothing exciting here:

1. We create a ``Deferred`` to be returned by our ``slow_square`` function (line 5)
2. We create a function ``resolve`` (a closure) in which we resolve the previously created Deferred with the result (lines 7-8)
3. Then we ask the Twisted reactor to call ``resolve`` after 1 second (line 10)
4. And we return the previously created Deferred to the caller (line 11)

What you can see even with this trivial example already is that the code looks quite differently from synchronous/blocking code. It needs some practice until such code becomes natural to read.

Now, when converted to ``inlineCallbacks``, the code becomes:

.. code-block:: python
   :linenos:
   :emphasize-lines: 5,7,8

   from twisted.internet import reactor
   from twisted.internet.defer import inlineCallbacks, returnValue
   from autobahn.twisted.util import sleep

   @inlineCallbacks
   def slow_square(x):
      yield sleep(1)
      returnValue(x * x)

   @inlineCallbacks
   def test():
      res = yield slow_square(3)
      print(res)
      reactor.stop()

   test()
   reactor.run()


Have a look at the highlighted lines - here is what we do:

1. Decorating our squaring function with ``inlineCallbacks`` (line 5). Doing so marks the function as a coroutine which allows us to use this sequential looking coding style.
2. Inside the function, we simulate the slow execution by sleeping for a second (line 7). However, we are sleeping in a non-blocking way (``autobahn.twisted.util.sleep``). The ``yield`` will put the coroutine aside until the sleep returns.
3. To return values from Twisted coroutines, we need to use ``returnValue`` (line 8).

.. note::

   The reason ``returnValue`` is necessary goes deep into implementation details of Twisted and Python. In short: co-routines in Python 2 with Twisted are simulated using exceptions. Only Python 3.3+ has gotten native support for co-routines using the new yield from statement, Python 3.5+ use await statement and it is the new recommended method.

In above, we are using a little helper ``autobahn.twisted.util.sleep`` for sleeping "inline". The helper is really trivial:

.. code-block:: python

   from twisted.internet import reactor
   from twisted.internet.defer import Deferred

   def sleep(delay):
      d = Deferred()
      reactor.callLater(delay, d.callback, None)
      return d

The rest of the program is just for driving our test function and running a Twisted reactor.



Asyncio Futures and Coroutines
..............................


`Asyncio Futures <http://docs.python.org/3.4/library/asyncio-task.html#future>`__ like Twisted Deferreds encapsulate the result of a future computation. At the time of creation, the result is (usually) not yet available, and will only be available eventually.

On the other hand, asyncio futures are quite different from Twisted Deferreds. One difference is that they have no built-in machinery for chaining.

`Asyncio Coroutines <http://docs.python.org/3.5/library/asyncio-task.html#coroutines>`__ are (on a certain level) quite similar to Twisted inline callbacks. Here is the code corresponding to our example above:


-------

**Example**

The following demonstrates basic usage of ``asyncio.coroutine`` in a complete example you can run.

First, consider this program using plain ``asyncio.Future``. We simulate calling a slow function by sleeping (without blocking) inside the function ``slow_square``

.. code-block:: python
   :linenos:
   :emphasize-lines: 4,6-7,10,12

   import asyncio

   def slow_square(x):
      f = asyncio.Future()

      def resolve():
         f.set_result(x * x)

      loop = asyncio.get_event_loop()
      loop.call_later(1, resolve)

      return f

   def test():
      f = slow_square(3)

      def done(f):
         res = f.result()
         print(res)

      f.add_done_callback(done)

      return f

   loop = asyncio.get_event_loop()
   loop.run_until_complete(test())
   loop.close()

Using asyncio in this way is probably quite unusual. This is because asyncio is opinionated towards using coroutines from the beginning. Anyway, here is what above code does:

1. We create a ``Future`` to be returned by our ``slow_square`` function (line 4)
2. We create a function ``resolve`` (a closure) in which we resolve the previously created Future with the result (lines 6-7)
3. Then we ask the asyncio event loop to call ``resolve`` after 1 second (line 10)
4. And we return the previously created Future to the caller (line 12)


What you can see even with this trivial example already is that the code looks quite differently from synchronous/blocking code. It needs some practice until such code becomes natural to read.

Now, when converted to ``asyncio.coroutine``, the code becomes:

.. code-block:: python
   :linenos:
   :emphasize-lines: 3,4,5

   import asyncio

   async def slow_square(x):
      await asyncio.sleep(1)
      return x * x


   async def test():
      res = await slow_square(3)
      print(res)

   loop = asyncio.get_event_loop()
   loop.run_until_complete(test())

The main differences (on surface) are:

1. The declaration of the function with ``async`` keyword (line 3) in asyncio versus the decorator ``@defer.inlineCallbacks`` with Twisted
2. The use of ``defer.returnValue`` in Twisted for returning values whereas in asyncio, you can use plain returns (line 6)
3. The use of ``await`` in asyncio, versus ``yield`` in Twisted (line 5)
4. The auxiliary code to get the event loop started and stopped

Most of the examples that follow will show code for both Twisted and asyncio, unless the conversion is trivial.