Python Concurrency Explained: Threading vs Asyncio Made Simple

  • Posted on October 7, 2025
  • Technology
  • By MmantraTech
  • 59 Views

I tried to write this in simple words so you can just copy and run the code. It might sound a bit casual, but I promise it’s easy to follow.

What is Threading vs Asyncio and when to pick which

3D flat tech scene showing two parallel paths labeled “Threading” and “Asyncio” with a Python developer choosing between them; modern flat design in blue tones, style like mmantratech.com_blog_latest; over-vYxfKfeTsK.jpg

In short, threading vs asyncio compares two main ways to handle concurrency in Python. Threading uses OS-level threads — great for blocking I/O tasks or parallel work. Asyncio uses cooperative multitasking, where a single thread manages many async tasks. In my experience, asyncio feels more lightweight for network tasks, while threading is great when you already use blocking code.


Threading

  • Each thread runs separately — like multiple “workers” doing tasks in parallel.

  • OS-level threads, managed by the operating system.

  • Good for I/O-bound tasks (like file download, API calls).

  • Uses more memory since each thread has its own stack.

import threading
def task(n):
    print(f"Task {n}")
threading.Thread(target=task, args=(1,)).start()
import threading
import time


class MyThread(threading.Thread):

    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        for i in range(3):
            time.sleep(1)
            print(f"Thread {self.name} is running ")


ob1 = MyThread("Thread-1")
ob1.start()
ob2 = MyThread("Thread-2")
ob2.start()

info = "Current thread name %s" % threading.current_thread().name
print(info)

ob1.join()
ob2.join()

# Print names of all active threads
for thread in threading.enumerate():
    print("Thread name: %s" % thread.name)
# You can also get the number of active threads using:
print("Active thread count:", threading.active_count())

Asyncio

  • Single-threaded but asynchronous — it switches between tasks while waiting (non-blocking).

  • Managed by Python’s event loop, not OS threads.

  • Also great for I/O-bound work, but uses less memory and scales better with thousands of tasks.

import time


def greet(name):
    print(f"Hello, {name}!")
    time.sleep(2)
    print(f"Goodbye, {name}!")


def main():
    greet("kamal")
    greet("suresh")
    greet("rajesh")


main()
import asyncio

async def task(n):
    print(f"Task {n}")

asyncio.run(task(1))
import time
import asyncio


async def greet(name):
    print(f"Hello, {name}!")
    await asyncio.sleep(2)
    print(f"Goodbye, {name}!")


async def main():
    await asyncio.gather(greet("kamal"), greet("suresh"), greet("rajesh"))


asyncio.run(main())


Ways to use Threading in Python

1. Basic threading.Thread example

This is the simplest way to create and run multiple threads.

 
# keyword: threading vs asyncio
import threading
import time

def worker(n):
    print(f"Worker {n} start")
    time.sleep(1)
    print(f"Worker {n} done")

threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print("All threads finished")
 

Each worker runs in parallel using multiple OS threads. You’ll see that all three threads sleep at the same time — perfect for blocking I/O.

2. ThreadPoolExecutor for easier threading

If you have many short blocking tasks, use concurrent.futures.ThreadPoolExecutor. It manages a pool of threads automatically.

 
from concurrent.futures import ThreadPoolExecutor
import time

def task(x):
    time.sleep(1)
    return x * 2

with ThreadPoolExecutor(max_workers=4) as ex:
    results = list(ex.map(task, range(6)))

print(results)  # [0, 2, 4, 6, 8, 10]
 

Ways to use Asyncio in Python

Asyncio works differently. It runs everything in a single thread using an event loop and async functions.

1. Basic asyncio.run with async functions

 
# keyword: threading vs asyncio
import asyncio

async def worker(n):
    print(f"Async worker {n} start")
    await asyncio.sleep(1)
    print(f"Async worker {n} done")

async def main():
    await asyncio.gather(*(worker(i) for i in range(3)))

asyncio.run(main())
 

This code runs three coroutines “together” on one thread. Each waits using await instead of blocking.

2. Creating tasks with asyncio.create_task

You can create and manage async tasks directly if you want finer control.

 
import asyncio

async def long_task(n):
    await asyncio.sleep(n)
    return n

async def main():
    tasks = [asyncio.create_task(long_task(i)) for i in (1, 2, 3)]
    done, _ = await asyncio.wait(tasks)
    print([t.result() for t in done])

asyncio.run(main())
 

When to use Threading vs Asyncio — simple rules

  • Use Threading when your code has blocking I/O (e.g., file operations or non-async libraries).
  • Use Asyncio when you handle many network I/O calls with async libraries like aiohttp.
  • Use Multiprocessing if your work is CPU-heavy (since threads share GIL).

Mixing Asyncio and Threading

Sometimes you need both. You can run blocking functions inside an executor from asyncio. This trick helped me when converting old synchronous code to async.

 
import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

def blocking_work(x):
    time.sleep(1)
    return x * 2

async def main():
    loop = asyncio.get_running_loop()
    with ThreadPoolExecutor() as pool:
        results = await asyncio.gather(*(loop.run_in_executor(pool, blocking_work, i) for i in range(4)))
    print(results)

asyncio.run(main())
 

Practical comparison

  • Threads: Higher memory cost, true OS-level concurrency for I/O.
  • Asyncio: Lightweight, single-threaded, best for many concurrent I/O tasks.
  • Context switching: Threads depend on OS; Asyncio switches tasks internally — much faster.

Conclusion

To sum up, threading vs asyncio in Python depends on your task type. Threading is perfect for blocking I/O and easy migration, while asyncio shines for handling thousands of async network calls. In my experience, I start with threading for simplicity, and move to asyncio when I need more scale and speed.

0
Author
No Image
Admin
MmantraTech

Mmantra Tech is a online platform that provides knowledge (in the form of blog and articles) into a wide range of subjects .

You May Also Like

Write a Response