Live data from Hacker News

Python and Async Simplified (2018)

aeracode.org

21–30 of 47 posts

Re: Python and Async Simplified (2018)

#21
post #20
post #12

I thought Python couldn't multithread because of GIL? I understood from the article that async derives all its benefit from certain OS-level operations which don't need to run in a coroutine, like reading from a network socket or waiting for timers to finish. Another question: Is Python's implementation of async/await identical to other languages? In particular, do they always use coroutines instead of threads?

since it's my job to clear these things up, a few pointers: 1. python has threads. they just cannot perform CPU bound tasks in parallel due to the GIL. The GIL is released for IO, so threads can perform IO waiting in parallel, just like asyncio 2. asyncio runs in one thread, and has the exact same limitations as threads as implemented in Python, CPU operations are serialized, async tasks can yield for IO. the advanta…

The GIL gets even more complicated than that because it can also be released during CPU bound tasks that don't interact with python objects (e.g. Array operations in numpy)

Re: Python and Async Simplified (2018)

#22
I am really interested in this space.

There's an article that Cal Paterson wrote that async doesn't speed up code - it is not parallel. The GIL prevents Python from being parallel. So even if you create a thread to run an async method in Python, it shall not run in parallel to the main thread of execution. (In fact, it shall block the main thread of execution if you start a thread in the thread you are in, due to the blocking run_in_executor)

https://calpaterson.com/async-python-is-not-faster.html

I wrote a multithreaded userspace 1:M:N scheduler (1 scheduler thread, M kernel threads and N lightweight/green threads) which resembles Golang M:N model. I implemented the same design in Rust, C and Java. I am thinking it could be combined with my epoll-server and it would be an application server.

https://github.com/samsquire/preemptible-thread https://github.com/samsquire/epoll-server

I am also interested in structured concurrency. This article by Vala developers is good.

https://verdagon.dev/blog/seamless-fearless-structured-concu...

I am trying to find a concurrent software design that is scalable and is easy to write and hides complicated lock programming. I document my studies and ideas in the open in ideas4.

https://github.com/samsquire/ideas4

I've implemented multithreaded parallel multiversion concurrency control in Java, which is the same approach used by Postgresql and MySQL for concurrent read and writing to the same data atomically.

I still think concurrency is hard to write and understand. Even with async/await.

// 3 requests in flight

result1 = async_task1();

result2 = async_task2();

result3 = async_task3();

await result1;

await result2;

await result3;

I ported a parallel multiconsumer multiproducer ringbuffer from Alek

https://www.linuxjournal.com/content/lock-free-multi-produce...

I use Python threads in https://github.com/samsquire/devops-schedule and https://github.com/samsquire/parallel-workers to parallelise a topologically sorted graph of IO of devops programs. This allows efficient scheduling and blocking with thread.join() for each split of the work graph and then a regrouping before doing other things, also potentially in parallel. This pattern is efficient and easy to use.

Re: Python and Async Simplified (2018)

#23

Earlier quoted context omitted.

> you should always explicitly delimitate the life cycle of any task Unless you want a hacky actor system, in which case it's totally fine to `create_task` a ton of corountines which have their own spin loop with await sleep :)

Even if you want to ‘fire and forget’, it’s still essential to keep a reference to the task, otherwise it can be garbage collected mid-execution: https://docs.python.org/3/library/asyncio-task.html#asyncio....

Wow! Did not know this, guess I’ve got a couple fixes to make…

Re: Python and Async Simplified (2018)

#24
post #4

Similar question to the other one at the time of writing, but more specific: does anyone have a good, thorough introduction to the "async event loop" (sometimes known as "asyncio") pattern? By thorough I mean that it goes beyond a starter tutorial, into both examples of various supporting libraries and implementation details that matter for usage. I'm fine with a book, too. There are popular libraries for it in both…

I found this post to be amazing intro that shows you how to go from simple generators to async event loop.

https://mleue.com/posts/yield-to-async-await/

Re: Python and Async Simplified (2018)

#25

It gives good pointers but it falls short on the usual suspects for an article on asyncio. When teaching it, it's important to emphasis: - await is locally blocking, so you should isolate linear workflows into their own coro, which is the unit of concurrency. - to allow concurrency, you should use asyncio.create_task on coro (formerly ensure_future). - you should always explicitly delimitate the life cycle of any tas…

> to allow concurrency, you should use asyncio.create_task on coro (formerly ensure_future).

This is misleading... you can use asyncio.gather which does this internally [0].

[0]: https://github.com/python/cpython/blob/main/Lib/asyncio/task...

Re: Python and Async Simplified (2018)

#26
post #20
post #12

I thought Python couldn't multithread because of GIL? I understood from the article that async derives all its benefit from certain OS-level operations which don't need to run in a coroutine, like reading from a network socket or waiting for timers to finish. Another question: Is Python's implementation of async/await identical to other languages? In particular, do they always use coroutines instead of threads?

since it's my job to clear these things up, a few pointers: 1. python has threads. they just cannot perform CPU bound tasks in parallel due to the GIL. The GIL is released for IO, so threads can perform IO waiting in parallel, just like asyncio 2. asyncio runs in one thread, and has the exact same limitations as threads as implemented in Python, CPU operations are serialized, async tasks can yield for IO. the advanta…

I've been coding Python since 2.5 days and I have yet to have a use case where I've really needed asyncio. For client-side code, concurrent.futures (specifically ThreadPoolExecutor) has satisfied nearly every use case, though occasionally I'll use a a worker-thread model.

For server-side code, I'd still probably use threads up to maybe 1000 concurrent connections. Beyond that, I've used gevent to good effect. e.g., I have a server that receives HTTP POSTs which are multipart forms, the form having 3 parts, a JSON part and two file parts. The two files parts get written to files on S3 and the JSON part to SQS. The web framework is Falcon[1] and I also made use of a Cython-based HTTP form parser[2]. Concurrency is handled via gevent. Openresty sits in front and invokes the Python server via uwsgi. At the time I developed it, asyncio was not yet mature and not supported by boto3. I benchmarked against pypy but unsurprisingly (since it's I/O bound) got better performance and from CPython + gevent.

If I were developing it from scratch today, I'd re-evaluate the asyncio story, or more likely than not, choose a different language.

I don't doubt that there's use-cases to which asyncio is well-suited and the right choice, but I suspect folks may be using it in cases where they'd be fine with threads. As always, there are trade-offs.

1. https://falconframework.org/

2. https://pypi.org/project/streaming-form-data/ (I think)

Re: Python and Async Simplified (2018)

#27
post #12

I thought Python couldn't multithread because of GIL? I understood from the article that async derives all its benefit from certain OS-level operations which don't need to run in a coroutine, like reading from a network socket or waiting for timers to finish. Another question: Is Python's implementation of async/await identical to other languages? In particular, do they always use coroutines instead of threads?

> I thought Python couldn't multithread because of GIL?

Why would it need to in this case? You only need one thread for concurrent I/O.

Re: Python and Async Simplified (2018)

#28
post #21
post #20

Earlier quoted context omitted.

since it's my job to clear these things up, a few pointers: 1. python has threads. they just cannot perform CPU bound tasks in parallel due to the GIL. The GIL is released for IO, so threads can perform IO waiting in parallel, just like asyncio 2. asyncio runs in one thread, and has the exact same limitations as threads as implemented in Python, CPU operations are serialized, async tasks can yield for IO. the advanta…

The GIL gets even more complicated than that because it can also be released during CPU bound tasks that don't interact with python objects (e.g. Array operations in numpy)

Right, if your cpu tasks are running in native extensions, then threading will actually allow parallelism, whereas asyncio will not.

Re: Python and Async Simplified (2018)

#29

I am really interested in this space. There's an article that Cal Paterson wrote that async doesn't speed up code - it is not parallel. The GIL prevents Python from being parallel. So even if you create a thread to run an async method in Python, it shall not run in parallel to the main thread of execution. (In fact, it shall block the main thread of execution if you start a thread in the thread you are in, due to the…

  // 3 requests in flight

  result1 = async_task1();

  result2 = async_task2();

  result3 = async_task3();
Depends on implementation, some are eager, some are lazy.

Re: Python and Async Simplified (2018)

#30
This is the bit that should be at the very top of the official docs. It's tripped me up every time I go to write async code and until you learn it, the error message is very confusing.

> In particular, calling it will immediately return a coroutine object, which basically says "I can run the coroutine with the arguments you called with and return a result when you await me".

> The code in the target function isn't called yet - this is merely a promise that the code will run and you'll get a result back, but you need to give it to the event loop to do that.

If I try to pass the async function to gather (for example) without calling it, which makes some intuitive sense, since functions are first class objects and I know I'm not calling it, the event loop is, the error message reads something like, "gather only accepts coroutines." But I thought it was a coroutine because I declared it with async! For some reason it took me a silly amount of time to notice that in all the examples, the async function is called when it's passed to gather (or whatever). That's not intuitive to me and the distinction made in the article should be clearer in the docs.

Post reply on HN