The Long Road To Asynchrony

The Long Road To Asynchrony

My keynote from PyCon Belarus 2020.

077e9a0cb34fa3eba2699240c9509717?s=128

Andrew Godwin

February 22, 2020
Tweet

Transcript

  1. LONG ROAD ANDREW GODWIN // @andrewgodwin ASYNCHRONY TO THE

  2. Andrew Godwin / @andrewgodwin Hi, I’m Andrew Godwin • Django

    core developer • Worked on Migrations, Channels & Async • Once a Londoner, now from Denver, USA
  3. Andrew Godwin / @andrewgodwin

  4. Andrew Godwin / @andrewgodwin "Asynchronous Programming" What is it, really?

  5. Andrew Godwin / @andrewgodwin Concurrent Programming The more general term

  6. Andrew Godwin / @andrewgodwin Input Process Output Sequential execution

  7. Andrew Godwin / @andrewgodwin Input Process Output Process Concurrent execution

    Archive
  8. Andrew Godwin / @andrewgodwin Shared use of a single resource

    In this case, CPUs
  9. Andrew Godwin / @andrewgodwin "Communicating Sequential Processes", C. A. R

    Hoare
  10. Andrew Godwin / @andrewgodwin func main() { messages := make(chan

    string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) }
  11. Andrew Godwin / @andrewgodwin Multiple processes multiprocessing Threads threading Event

    loops asyncio / twisted
  12. Andrew Godwin / @andrewgodwin Multiple processes scale best It's also

    difficult and costs the most!
  13. Andrew Godwin / @andrewgodwin Threads are unpredictable Also, the GIL

    is our ever-present friend
  14. Andrew Godwin / @andrewgodwin Event loops are a good compromise

    They do require shared memory, though.
  15. Andrew Godwin / @andrewgodwin Asynchronous ≈ Event loops Most of

    the time!
  16. Andrew Godwin / @andrewgodwin # Ready when a timer finishes

    await asyncio.sleep(1) # Ready when network packets return await client.get("http://example.com") # Ready when the coroutine exits await my_function("hello", 64.2)
  17. Andrew Godwin / @andrewgodwin Network/timer updates An event loop's flow

    Select a ready task Run task Add new tasks to queue await
  18. Andrew Godwin / @andrewgodwin Coroutines Time →

  19. Andrew Godwin / @andrewgodwin How did we get here?

  20. Andrew Godwin / @andrewgodwin 1998 threading module, Stackless Python 2002

    Twisted 2006 Greenlets (later gevent, eventlet) 2008 multiprocessing module 2012 Tulip, PEP 3156 2014 asyncio module 2005 Coroutine-friendly generators (PEP 342)
  21. Andrew Godwin / @andrewgodwin 2017 Django Channels 1.0 2018 Django

    Channels 2.0 2019 DEP 9 (Async support) 2020 Async views land in Django
  22. Andrew Godwin / @andrewgodwin No solution is perfect Everyone chooses

    different tradeoffs
  23. Andrew Godwin / @andrewgodwin Asyncio is based on yield from

    Because it was prototyped in Python 2
  24. Andrew Godwin / @andrewgodwin Can't tell if a function returns

    a coroutine! There are standard hints, but no actual guaranteed way
  25. Andrew Godwin / @andrewgodwin async def calculate(x): result = await

    coroutine(x) return result # These both return a coroutine def calculate(x): result = coroutine(x) return result
  26. Andrew Godwin / @andrewgodwin Can't have one function service both

    How we got here makes sense, but it's still annoying sometimes.
  27. Andrew Godwin / @andrewgodwin # Calls get.__call__ instance = MyModel.objects.get(id=3)

    # Calls get.__call__ # and then awaits its result instance = await MyModel.objects.get(id=3)
  28. Andrew Godwin / @andrewgodwin You have to namespace async functions

    I really, really wish we didn't have to
  29. Andrew Godwin / @andrewgodwin instance = MyModel.objects.get(id=3) instance = await

    MyModel.objects.async.get(id=3)
  30. Andrew Godwin / @andrewgodwin Completely different libraries! Even sleep() is

    different.
  31. Andrew Godwin / @andrewgodwin time.sleep ➞ asyncio.sleep requests ➞ httpx

    psycopg2 ➞ aiopg WSGI ➞ ASGI Django ➞ Django?
  32. Andrew Godwin / @andrewgodwin Django & Async

  33. Andrew Godwin / @andrewgodwin Asyncio only benefits IO-bound code Code

    that thrashes the CPU doesn't benefit at all
  34. Andrew Godwin / @andrewgodwin We're adding async to some parts

    The bits where it makes sense!
  35. Andrew Godwin / @andrewgodwin But, you can't mix sync and

    async So we have to have two parallel request paths
  36. Andrew Godwin / @andrewgodwin WSGIHandler __call__ WSGI Server WSGIRequest BaseHandler

    get_response URLs Middleware View __call__ HTTP protocol Socket handling Transfer encodings Headers-to-META Upload file wrapping GET/POST parsing Exception catching Atomic view wrapper Django 3.0 Request Flow
  37. Andrew Godwin / @andrewgodwin WSGIHandler __call__ WSGI Server WSGIRequest BaseHandler

    get_response URLs Middleware Async View __call__ ASGIHandler __call__ ASGI Server ASGIRequest Sync View __call__ Asynchronous request path Proposed async request flow
  38. Andrew Godwin / @andrewgodwin WSGIHandler __call__ WSGI Server WSGIRequest URLs

    Middleware View __call__ ASGIHandler __call__ ASGI Server ASGIRequest Asynchronous request path BaseHandler get_response_async BaseHandler get_response URLs Middleware Async View __call__ Implemented async request flow
  39. Andrew Godwin / @andrewgodwin We have to work with what

    we have I'd rather let people ship code than argue about perfection.
  40. Andrew Godwin / @andrewgodwin Django's main job is safety It

    matters more than anything else
  41. Andrew Godwin / @andrewgodwin Deadlocks Livelocks Starvation Race conditions

  42. Andrew Godwin / @andrewgodwin Coroutines & the GIL actually help!

    You're saved from all the awful memory corruption bugs
  43. Andrew Godwin / @andrewgodwin async def transfer_money(p1, p2): await get_lock()

    # Code between awaits is atomic! subtract_money(p1) add_money(p2) await release_lock()
  44. Andrew Godwin / @andrewgodwin You can still screw up a

    lot Trust me, I have lived it while developing async
  45. Andrew Godwin / @andrewgodwin async def log_message(m): await client.post("log-server", m)

    result = calculate_result() log_message(m)
  46. Andrew Godwin / @andrewgodwin async def log_message(m): await client.post("log-server", m)

    result = calculate_result() await log_message(m)
  47. Andrew Godwin / @andrewgodwin Async usability has a long way

    to go But it is undoubtedly the future!
  48. Andrew Godwin / @andrewgodwin Python is the language of pragmatism

    If anyone can get it right, we can
  49. Andrew Godwin / @andrewgodwin What does the future hold? Hopefully,

    no GIL!
  50. Andrew Godwin / @andrewgodwin Let's make async understandable Almost every

    project could benefit, if we made it worth their time.
  51. Thanks. Andrew Godwin @andrewgodwin // aeracode.org