Surprised nobody mentioned asyncio.run_in_executor yet. It's designed to offload the event loop from long running cpu bound tasks, by moving them to another thread pool (or process pool if you are afraid of GIL). Eventually that thread pool will obviously also get starved given enough load but at least you wont have CPU blocking IO and vice versa. Tricky thing is knowing when an operation might grow to become too slow for io-thread given dynamic inputs.
that's because `run_in_executor` doesn't spread CPU usage. All it does is wrap functions in threads so you can call them async. It doesn't create multiple processes so you're still limited to a single core in Python.
https://docs.python.org/3/library/asyncio-eventloop.html#asy...