Is asyncio concurrent or parallel python?

Mar 17, 2024 ยท 4 min read

As Python developers, we often need to write programs that perform multiple tasks concurrently for high performance and throughput. This introduces the question - is asyncio truly concurrent or does it run tasks in parallel?

Let's explore the difference between concurrency and parallelism and how asyncio allows concurrency in Python.

Concurrency vs Parallelism

Concurrency refers to the ability to deal with lots of things at once. A concurrent system can handle multiple tasks by switching between them.

Parallelism is the ability to literally run multiple tasks at the exact same time, e.g. on multi-core CPUs.

The key difference is that concurrent systems handle multiple tasks by switching between them quickly, while parallel systems run tasks simultaneously.

How asyncio Works

The asyncio module introduced concurrency in Python 3 using a single-threaded, non-blocking approach. Here's a quick overview:

  • Asyncio provides an event loop which runs tasks and switches between them.
  • Tasks are executed cooperatively by suspending themselves when waiting for I/O. This allows other tasks to run in the meantime.
  • Asyncio uses futures and coroutines to make writing concurrent code simpler.
  • So asyncio enables concurrency, not parallelism. It runs multiple tasks on a single thread by switching context cooperatively.

    Achieving High Performance

    The fact that asyncio doesn't utilize multiple cores may seem like a disadvantage. However, properly written asyncio code can rival parallel solutions in performance. Here's why:

    Avoid Slow I/O Bound Work

    Asyncio shines when dealing with high latency I/O bound work like network calls and disk access. By switching tasks instead of blocking threads, you prevent wasting cycles waiting.

    Scale with Less Resources

    An asyncio server can handle thousands of connections on a single thread and process. Parallel solutions require more memory and threads to scale.

    Use asyncio and Multiprocessing

    For CPU heavy tasks, you can delegate work to process pools. This provides parallelism when needed while still using asyncio for I/O.

    Here's an example:

    import asyncio
    from multiprocessing import Pool
    
    async def cpu_heavy(n):
        return pow(2, n)
        
    def main():
        pool = Pool() 
        loop = asyncio.get_event_loop()
        
        futures = [loop.run_in_executor(pool, cpu_heavy, 2**i) 
                   for i in range(20)]
                   
        loop.run_until_complete(asyncio.wait(futures))
        
        for future in futures:
            print(future.result())
    
    main()

    This runs multiple CPU heavy tasks in a process pool while the main thread uses asyncio coordination.

    When to Use Asyncio

    Here are some examples of workloads where asyncio shines:

  • Network services - asyncio excels at handling many connections and requests efficiently.
  • Web scraping - easily scrape multiple web pages in parallel.
  • Database access - query databases concurrently without blocking threads.
  • Microservices - coordinate and scale multiple services using asyncio.
  • Any I/O heavy workload will benefit from asyncio's cooperative multitasking approach.

    Common Pitfalls

    Here are some things to avoid when writing asyncio code:

  • Blocking calls - any blocking call halts the event loop until it finishes. Use non-blocking alternatives.
  • CPU heavy work - delegate expensive computations to a thread or process pool.
  • Unstructured callbacks - nesting lots of callbacks can lead to messy code. Use asyncio.create_task.
  • Shared state - with multiple tasks running concurrently, shared state can introduce race conditions. Use synchronization primitives like queues, locks etc.
  • Properly structured asyncio code avoids these issues and unlocks excellent performance.

    Key Takeaways

  • Asyncio provides concurrency, not parallelism. It uses cooperative multitasking to switch tasks on a single thread.
  • Performance can match parallel solutions for I/O bound work despite using one thread.
  • Use multiprocessing to add parallelism for CPU intensive tasks.
  • Asyncio shines for workloads like network and database access, web scraping etc.
  • Avoid blocking calls, expensive CPU work, messy callbacks and shared state.
  • So while asyncio isn't parallel, you can certainly achieve high throughput by mastering this performant single-threaded concurrency framework.

    I hope this overview helps explain the difference between concurrency and parallelism in Python.

    Browse by tags:

    Browse by language:

    The easiest way to do Web Scraping

    Get HTML from any page with a simple API call. We handle proxy rotation, browser identities, automatic retries, CAPTCHAs, JavaScript rendering, etc automatically for you


    Try ProxiesAPI for free

    curl "http://api.proxiesapi.com/?key=API_KEY&url=https://example.com"

    <!doctype html>
    <html>
    <head>
        <title>Example Domain</title>
        <meta charset="utf-8" />
        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    ...

    X

    Don't leave just yet!

    Enter your email below to claim your free API key: