⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenVorsicht: Android-Malware verschlüsselt Ihre Handys und nimmt heimlich Fotos auf(11.09.2026 um 09:35 Uhr)
🕵️ SicherheitslückenMicrosoft geht endlich eines der nervigsten Probleme von Windows 11 an(11.09.2026 um 11:58 Uhr)
💾 IT Security ToolsSysinternals Suite(11.09.2026 um 12:00 Uhr)
🕵️ SicherheitslückenDefender 0-Day ShieldBreak (CVE-2026-69414) nicht sauber gepatcht - BornCity(11.09.2026 um 12:52 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 15 Min Lesezeit
0

Python Concurrency: GIL, Threading, Multiprocessing and Asyncio

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Introduction



When I started learning concurrency in Python, the first thing that confused me was why there are so many different ways to execute multiple tasks.



We have threading, multiprocessing, and asyncio.



At first, they all seemed to solve the same problem: doing multiple things at once.



But then I realized that they don't solve exactly the same problem.



The real question is:



What is preventing our program from finishing faster?



Sometimes the CPU is busy doing calculations.



Sometimes the CPU is barely doing anything because the program is waiting for an API, database, file, or network response.



Once I understood this difference, threading, multiprocessing, the GIL, and asyncio started connecting naturally.









What You Will Learn




  • Why concurrency is needed

  • Concurrency vs parallelism

  • I/O-bound vs CPU-bound tasks

  • Why threading exists

  • What the GIL changes

  • Why multiprocessing is needed

  • Why asyncio exists even though we already have threading

  • Event loop

  • Coroutines


  • async and await

  • Tasks and Futures

  • asyncio.gather()

  • asyncio.wait()

  • asyncio.wait_for()

  • Cancellation

  • Cooperative concurrency









Where Does the Problem Start?



Imagine I have three API requests.



Each request takes around 5 seconds to return a response.



If I execute them normally:



request_a()

request_b()

request_c()



the first request starts and waits for its response.



Only after it finishes does the second request start.



Then the third.



So roughly:



A → wait 5 sec → finish



CODE
              B → wait 5 sec → finish

C → wait 5 sec → finish



Total time:



5 + 5 + 5 = 15 seconds



But something feels wrong here.



While A is waiting for the server, our CPU doesn't need to spend those entire 5 seconds calculating something for A.



A lot of that time is simply waiting.



So why can't we use that waiting time to make progress on B?



This is where concurrency starts becoming useful.







Concurrency



Concurrency means multiple tasks can make progress during overlapping periods of time.



Instead of:



A → WAIT → finish



CODE
         B → WAIT → finish

C → WAIT → finish



we want something closer to:



A → start → WAIT ─────────────→ finish



B → start → WAIT ────→ finish



C → start → WAIT → finish



When A cannot make progress because it is waiting, something else can make progress.



This does not necessarily mean A and B are executing CPU instructions at exactly the same moment.



That leads to another concept I initially mixed up with concurrency.







Concurrency vs Parallelism



Concurrency is about handling multiple tasks over overlapping time.



Parallelism means multiple tasks are literally executing at the same time.



Imagine one CPU core.



It can do something like:



A → execute

A → wait



B → execute

B → wait



A → resume



That's concurrency.



Now imagine multiple CPU cores:



Core 1 → Task A



Core 2 → Task B

Now A and B can actually execute simultaneously.



That's parallelism.



So I started thinking about it like this:



Concurrency

→ multiple tasks make progress over overlapping time



Parallelism

→ multiple tasks actually execute at the same time



But now another question comes.



How do I know whether my program needs concurrency or parallelism?



For that, I first need to know what kind of work my program is doing.







I/O-Bound vs CPU-Bound



This distinction connects almost everything else.



Suppose I make an API request.



The CPU might spend a tiny amount of time sending the request and processing the response.



But most of the time looks like:



Send request



WAIT

WAIT

WAIT

WAIT



Response



This is an I/O-bound task.



Examples include:




  • API requests

  • Database queries

  • Network operations

  • File operations



The important part is:



Most of the time is spent waiting.



Now consider:



total = 0



for i in range(100_000_000):

total += i * i



Here the CPU is constantly calculating.



CALCULATE

CALCULATE

CALCULATE

CALCULATE



There isn't much waiting time.



This is CPU-bound work.



Examples include heavy calculations, some data processing, image processing, and computation-heavy algorithms.



Now the problem becomes much clearer.



For I/O-bound work, I want to make use of the time where one operation is waiting.



For CPU-bound work, I want more actual CPU execution power.



This is where threading enters.







Why Threading?



Imagine:



API Request A → waiting



API Request B → ready to start



There is no reason for B to wait just because A is waiting for the network.



With threading, we can have multiple threads inside the same process.



Python Process



├── Thread A

├── Thread B

└── Thread C



Suppose Thread A makes an API request.



Thread A



send request



WAIT



While A is blocked waiting for I/O, another thread can make progress.



Thread A → WAITING



Thread B → RUNNING



This makes threading useful for many I/O-bound problems.



The threads also share the memory of their process, which makes communication between them relatively straightforward, although shared mutable data can create synchronization problems.



At this point I had another thought.



If threads can execute my work, and my laptop has multiple CPU cores, why don't I just use threads for CPU-heavy calculations too?



That's where the GIL becomes important.







The GIL



In standard GIL-enabled CPython, there is something called the Global Interpreter Lock.



Consider:



ONE Python Process



CPython Interpreter



GIL



┌──────┼──────┐

↓ ↓ ↓

T1 T2 T3



Multiple threads can exist.



But for execution of Python bytecode in that interpreter, the threads contend for the GIL.



This means that creating four threads for four heavy pure-Python calculations doesn't normally mean those four threads will execute Python bytecode simultaneously across four CPU cores.



This was an important correction to my original thinking.



I initially thought:




If the threads are working with different variables, why should Python stop them?




But the GIL isn't locking only the variables I created.



It is part of CPython's interpreter/runtime design and historically simplifies and protects important internal object and memory-management operations.



So even if:



Thread A → variable x



Thread B → variable y



both threads still execute within the same interpreter and contend for its GIL.



This explains why threading is often excellent for I/O-bound work but isn't normally the solution for getting multi-core parallelism from CPU-heavy pure-Python code.



So now there is another problem.



How do we actually use multiple CPU cores for Python calculations?







Why Multiprocessing?



Instead of creating multiple threads inside one process, we can create multiple processes.



Process A



Process B



Process C



Each process normally has its own address space and Python interpreter state.



Conceptually:



Process A

→ Interpreter A

→ GIL A



Process B

→ Interpreter B

→ GIL B



Now the operating system can potentially schedule them on different CPU cores.



Core 1 → Process A



Core 2 → Process B



This gives us actual CPU parallelism when hardware resources are available.



That's why multiprocessing makes sense for CPU-bound pure-Python work.



But multiprocessing introduces its own trade-off.







Threads Share Memory, Processes Don't Normally Share It



Suppose:



count = 0



With threads:



ONE PROCESS



Shared count



┌───┴───┐

↓ ↓

T1 T2




CODE

Both threads are inside the same process and can access its memory.

With multiprocessing:

Parent Process
count = 0

Process A
count = 0

Process B
count = 0


If A changes its `count` to 1 and B changes its `count` to 1, that doesn't automatically mean:

Parent count = 2


The processes normally have separate address spaces.

So multiprocessing gives us:


CPU parallelism

Separate processes

More isolation

But sharing data becomes more complicated


Now I understood the basic split:


I/O-bound
→ Threading

CPU-bound
→ Multiprocessing


But then I reached another question.

**If threading already handles I/O-bound work, why does Python have asyncio?**

---

# Why Asyncio If Threading Already Exists?

Imagine a server handling a very large number of network connections.

With a thread-per-operation design, we might imagine:

Connection 1 → Thread 1
Connection 2 → Thread 2
Connection 3 → Thread 3
...

This can work, but threads aren't free.

The operating system has to manage them, schedule them, switch between them, and allocate resources for them.

And the interesting part is that many network operations spend most of their time doing this:

WAITING


So I started thinking:

**Do I really need a large number of OS threads just to manage a large number of waiting operations?**

Asyncio gives us another model.

Instead of relying on one thread per async operation, an event loop, commonly running on one thread, can coordinate many asynchronous tasks.







text id="al7b5p"

ONE THREAD



EVENT LOOP



┌────┼────┐

↓ ↓ ↓

A B C




CODE

This is where the **event loop** becomes important.

---

# What Does the Event Loop Actually Do?

Suppose we have:

Task A
Task B
Task C


One event-loop thread can only execute one piece of Python code at an instant.

So something needs to keep track of:

Who can run?

Who is waiting?

Whose I/O has become ready?

Who should resume?


That's the job of the event loop.

Imagine A starts:

Event Loop

Task A


A sends an API request and cannot continue until the response arrives.

Instead of blocking the event-loop thread, A can suspend.

Now:

A → WAITING
B → READY
C → READY


The event loop can run B.

Later, when A's awaited operation becomes ready:
A → READY


the event loop can eventually resume A.

So I started thinking of the event loop as a **coordinator**.

It keeps async work moving based on what is ready and what is waiting.

But this creates another problem.

**How can a Python function stop in the middle and continue later?**

That's where coroutines enter.

---

# Coroutines

A normal function roughly behaves like:

START

execute

execute

RETURN


For asyncio, we need something capable of:

START

execute

SUSPEND

something else runs

RESUME

execute

RETURN


That's the important idea behind a coroutine.

In Python:

async def download():
print("Starting")

await asyncio.sleep(2)

print("Finished")

`async def` defines a coroutine function.

When we call it:


download()

we get a coroutine object representing that asynchronous operation.

The coroutine can later be awaited or scheduled.

Now we need something that allows the coroutine to suspend.

That's `await`.

---

# What Does `await` Really Mean?

Consider:


data = await get_data()
At first, I interpreted `await` as:

> Wait here until the result comes.

That is correct from the **coroutine's point of view**, but it misses the most important part.

The coroutine cannot continue past that line until `get_data()` is ready.

But if the awaited operation needs to wait and supports asynchronous suspension, the coroutine can give control back to the event loop.

Task A

await get_data()

result isn't ready

A SUSPENDS

Event Loop gets control

runs another ready task


Later:

get_data becomes ready

Task A becomes ready

Event Loop resumes A

data = result


This was the point where asyncio started making sense to me.

`await` doesn't mean:







text id="84k0f5"

Stop the entire program.




CODE

It means more like:

> I cannot continue until this awaited operation is ready. If I need to suspend, let the event loop make progress elsewhere.

But there is another important trap.

---

# `async` Doesn't Automatically Mean Concurrent

Suppose:







python id="8yr9go"

async def main():

await task_a()

await task_b()




CODE

If A takes 3 seconds and B takes 2 seconds, B isn't even reached until the first `await` completes.

So:







text id="s8z1op"

A → 3 seconds → finish



B → 2 seconds → finish




CODE

Total is around 5 seconds.

So just writing:







python id="5s46wq"

async def




CODE

doesn't automatically make everything concurrent.

Sometimes we want to tell the event loop:

> Schedule this coroutine so it can make progress independently while I do other async work.

This leads to **Tasks**.

---

# Tasks

Suppose:







python id="pivjwp"

task_a()




CODE

creates a coroutine object.

Now:







python id="e3cjbs"

asyncio.create_task(task_a())




CODE

schedules that coroutine with the running event loop as a Task.

Think:







text id="mt0pf2"

Coroutine



create_task()



Task



scheduled with Event Loop




CODE

Now we can do:







python id="52ks84"

a = asyncio.create_task(task_a())

b = asyncio.create_task(task_b())



await a

await b




CODE

Both A and B are scheduled before we wait for their completion.

So if A suspends:







text id="w55qv8"

A → await → suspend




CODE

B can make progress.

This gives us concurrent async execution.

---

# Then What Is a Future?

After understanding Tasks, Futures sounded complicated, but the underlying idea is simple.

A Future represents:

**A result that may not exist yet but should become available later.**

Imagine:







text id="v1u6m7"

API request



Future



PENDING



response arrives



DONE



result available




CODE

If I write:







python id="q53g1v"

result = await future




CODE

and the Future isn't complete, my coroutine can suspend.

When the Future becomes complete, the coroutine can resume with its result or receive its exception.

A useful relationship is:







text id="rz0pvz"

Future

→ represents an eventual result



Task

→ schedules/drives a coroutine

and also represents its eventual result




CODE

In asyncio, a Task is a specialized kind of Future.

---

# What If I Have Many Async Operations?

Suppose I have:







text id="rg2yio"

A

B

C




CODE

and I want all of them to run concurrently and then collect their results.

I could manually create and await Tasks.

But asyncio gives us:







python id="fdvev2"

results = await asyncio.gather(

task_a(),

task_b(),

task_c()

)




CODE

Conceptually:







text id="shludv"

A ──────────────┐

B ──────────┐ │

C ──────┐ │ │

↓ ↓ ↓

all complete



results




CODE

So I think of `gather()` as:

**Run/await these async operations concurrently and collect their results together.**

But sometimes I don't want to simply wait for everything.

Maybe I want more control.

---

# `asyncio.wait()`

Imagine:







text id="1rbsr4"

A → 10 sec

B → 2 sec

C → 6 sec




CODE

Maybe my requirement is:

> As soon as one finishes, tell me what is finished and what is still pending.

That's where `wait()` is useful.







python id="l82zyr"

done, pending = await asyncio.wait(

tasks,

return_when=asyncio.FIRST_COMPLETED

)




CODE

After around 2 seconds:







text id="f0qq5y"

DONE

→ B



PENDING

→ A

→ C




CODE

So the difference becomes clearer:







text id="nv8vgo"

gather()

→ collect results from async operations



wait()

→ give me control over done/pending tasks




CODE

But what if my problem isn't multiple tasks?

What if one operation is simply taking too long?

---

# `asyncio.wait_for()`

Suppose:







python id="wuy3st"

await get_data()




CODE

normally takes a few seconds.

But if the server has a problem, I don't want my program waiting indefinitely.

I can say:







python id="3uswb8"

await asyncio.wait_for(

get_data(),

timeout=5

)




CODE

Now I'm giving the operation a maximum waiting time.

If it doesn't finish within that timeout, `wait_for()` normally cancels the awaited operation and raises a timeout exception.

So:







text id="4hq10d"

wait()

→ completion control over tasks



wait_for()

→ timeout around an awaitable




CODE

This naturally brings us to cancellation.

---

# Cancellation

Suppose a download is running:







text id="wm16rk"

Downloading...

Downloading...

Downloading...




CODE

and the user clicks Cancel.

We can request cancellation of a Task:







python id="0vqtaj"

task.cancel()




CODE

But asyncio cancellation is **cooperative**.

It isn't best understood as Python violently killing arbitrary code at a random CPU instruction.

Cancellation is delivered through asyncio's task machinery, typically by raising `CancelledError` in the task at an appropriate point.

That allows the coroutine to clean up.

For example:







python id="5mr1rk"

async def download():



CODE
try:
await get_file()

except asyncio.CancelledError:
print("Cleaning up...")
raise





CODE

This might allow us to close a connection, remove a temporary file, or release some resource before stopping.

And this finally connects to one of the most important ideas behind asyncio.

---

# Cooperative Concurrency

Why is this called **cooperative concurrency**?

Because async tasks need to cooperate with the scheduler.

Imagine:






text id="78r1ks"

Task A



runs



await



gives control back



Event Loop



Task B




CODE

A reaches a point where it cannot make progress and allows the event loop to run other work.

Then B does the same.







text id="nn3etb"

A → run → await ───────────→ resume



B → run → await ─────→ resume




CODE

But consider:







python id="3ib4ap"

async def calculate():



CODE
while True:
do_heavy_calculation()





CODE

There is no useful asynchronous suspension point.

Even though the function says:






python id="dl81dq"

async def




CODE

the CPU-heavy code can occupy the event-loop thread and prevent other async tasks from getting a chance to run.

This is why `asyncio` isn't a replacement for multiprocessing.

They solve different problems.

---

# Connecting Everything

This is the flow that finally made the entire topic clear to me.

Start with the program:







text id="gwhicf"

My program is slow



Why?




CODE

There are two major possibilities:







text id="qx4c7o"

PROGRAM



Where is time spent?



┌────────┴────────┐

↓ ↓

WAITING CALCULATING

↓ ↓

I/O-BOUND CPU-BOUND




CODE

If the program is mostly waiting:







text id="0e3rd0"

I/O-bound



Need concurrency



Threading OR Asyncio




CODE

Threading:







text id="r1hj7k"

Multiple OS threads

inside a process




CODE

Asyncio:







text id="28ktn5"

Event loop



coordinates many

async operations



coroutines suspend

using await




CODE

If the program is mostly calculating:







text id="74sdq7"

CPU-bound



Threads in standard

GIL-enabled CPython

don't normally give us

multi-core Python-bytecode

parallelism



Multiprocessing



Separate processes



Multiple CPU cores




CODE

And inside asyncio itself:

async def

Coroutine

scheduled as Task

Event Loop

Run

await

Suspend

Run other ready work

Resume






Once I stopped learning threading, multiprocessing, GIL, asyncio, event loop, coroutines, and await as isolated definitions and instead followed the problem each concept solves, the entire topic became much easier to understand.



The main lesson I took from Python concurrency is:



Don't first ask, "Should I use threading, multiprocessing, or asyncio?"



First ask:



"What is my program doing most of the time — waiting or calculating?"



Once that is clear, choosing the right concurrency model becomes much easier.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Microsoft Phone Link Not Showing Messages on Windows 11? Fix It
1 Quelle
How to Enable Windows 11 Screen Savers
1 Quelle
Post-DEF CON phishing campaign delivered AMOS and NetSupport malware