Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Long Road To Asynchrony

The Long Road To Asynchrony

My keynote from PyCon Belarus 2020.

Andrew Godwin

February 22, 2020
Tweet

More Decks by Andrew Godwin

Other Decks in Programming

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