← All articles

Concurrency and Multithreading

Understanding concurrency, parallelism, and multithreading. How threads share memory, why concurrency matters for performance, and practical patterns with async Python.

5 min read

Concurrency is the ability of a system or program to make progress on multiple tasks over overlapping periods. Parallelism means executing work at the same instant, for example on multiple CPUCentral Processing Unit cores. Concurrency can allow better resource use and improve the overall performance of a system or program. In a concurrent system, tasks or processes may appear to be executing simultaneously, even if they are actually being interleaved or executed in parallel by multiple threads, processes, or coroutinesFunctions that can pause at an await point and resume later, so one thread can interleave many tasks.. The main goal of concurrency is to improve the responsiveness, throughput, and scalability of a system or program.

What is Concurrency?

Concurrency means that one task can begin before another finishes. Tasks can take turns on one thread, overlap I/OInput/Output waits, or run in parallel when the runtime and hardware allow it.

What is Multithreading?

Multithreading is a specific form of concurrency where a single process can have multiple threads executing concurrently. A thread is not a subprocess. Unlike processes, which don’t share memory by default and communicate through explicit IPCInter-Process Communication mechanisms (pipes, shared memory, sockets), threads share the same memory space and can interact with each other more efficiently.

Why is Concurrency Important?

  1. Efficiency: Concurrency helps applications handle multiple requests at the same time, making it faster and more responsive.

  2. Scalability: As your application grows, you’ll need to handle increasing amounts of data and tasks. Concurrent designs can overlap waits; parallel workers can also distribute suitable work across multiple cores.

  3. Resource Utilization: Overlapping waits can keep a CPU busy. Multiple cores and simultaneous multithreadingA CPU feature, such as Intel Hyper-Threading, that runs two or more hardware threads on one core. are different hardware features; concurrency alone guarantees neither full utilization nor lower energy use.

  4. User Experience: For applications that require real-time interaction, such as gaming or video streaming, concurrency is critical for smooth performance.

How Does Concurrency Work in Different Environments?

1. In a Single-Threaded Environment

Node.js normally runs JavaScript callbacks on one event-loopA loop that waits for events, such as finished I/O, and runs the callback registered for each one. thread, while the OSOperating System and a worker pool handle many I/O operations. Nonblocking I/O is well suited to this model. Long-running JavaScript and synchronous I/O can block the loop. See Node.js’s event-loop guidenodejs.orgNode.js guide on what blocks the event loop and the worker pool, and how to keep them free..

2. In a Multithreaded Environment

In multithreaded environments, you can create multiple threads to handle different tasks concurrently. For example:

  • One thread can be dedicated to accepting new connections (e.g., in a web server).
  • Another thread can process the request.
  • Yet another thread can handle writing the response.
One thread, one task at a timetask Atask Btask C270 msOne thread with an event loop (async)task Atask Btask C130 msThree threads on three corestask Atask Btask C90 ms0306090120150180210240270 msrunning on a CPUwaiting on I/O
Three tasks that each compute for 20 ms, wait 60 ms on I/O, then compute 10 ms more. One thread running them in turn takes 270 ms. An event loop fills the waits with other work and finishes in 130 ms. Three threads on three cores finish in 90 ms.

Why is Multithreading Important?

Multithreading is particularly important when dealing with I/O-bound operations, such as network requests or file operations. A blocking call makes its thread wait, but independent I/O operations need not be sequential. Threads or asynchronous I/O allow those waits to overlap with other work.

How to Implement Concurrency and Multithreading?

1. In JavaScript/Node.js

Node.js handles concurrency natively through its event loop and non-blocking async I/O. The event loop allows Node.js to perform I/O operations without blocking the main thread, delegating them to the system kernel or a thread pool (libuvThe C library that gives Node.js its event loop and asynchronous I/O.) when possible.

For example:

  • Use Promises and async/await patterns to handle multiple asynchronous operations concurrently.
  • Use Worker Threads for CPU-bound tasks that would otherwise block the event loop.
  • Use frameworks like Express or Koa for building web applications on top of Node.js’s concurrency model.

2. In Python

Python supports multithreading and multiprocessing to achieve concurrency. However, in a GILGlobal Interpreter Lock-enabled CPython interpreter, only one thread can execute Python bytecodeThe low-level instructions that the Python interpreter runs, compiled from the source code. at a time. This means multithreading is effective for I/O-bound tasks but offers limited performance gains for CPU-bound tasks.

For example:

  • Use the threading module for basic multithreading (best for I/O-bound tasks).
  • Use multiprocessing if you need to run CPU-bound tasks concurrently, bypassing the GIL by using separate processes.
  • Use asyncio with async/await patterns to handle multiple asynchronous operations concurrently.

Starting with Python 3.13, an experimental free-threaded mode (no-GIL) was introduced via the --disable-gil build flag (PEPPython Enhancement Proposal 703). In Python 3.14, this free-threaded build became officially supported while remaining optional (PEP 779). It permits parallel Python execution, but speedups depend on the workload and synchronization costs. Some extension modules can re-enable the GIL. Native extensions that release the GIL can also run in parallel in a GIL-enabled build. See Python’s free-threading guidedocs.python.orgPython documentation on the free-threaded build: how to install it, check for it and what to expect from it..

Practical Examples of Concurrency

Web Servers

Imagine you’re running a web server that handles multiple requests simultaneously. Without concurrency, each request would have to wait for the previous one to complete, leading to slow response times and high latency.

With concurrency:

  • One thread accepts new connections.
  • Another thread processes the request (e.g., reads data from the request and sends a response).

File Uploads

If you’re uploading files to a server, each upload can be handled by a different thread. This ensures that multiple uploads happen at the same time without waiting for one another.

Machine Learning Models

In machine learning applications, you might want to train multiple models simultaneously on different cores. Native numerical libraries may release the GIL and use their own threads; processes or accelerators are other options. Benchmark the workload, since competing thread pools can reduce performance.

Common Challenges in Concurrency

  1. Race Conditions: When multiple threads access shared data concurrently and at least one modifies it, the outcome depends on the timing of execution, leading to unpredictable results.
  2. Deadlocks: When two or more threads are each waiting for a resource held by the other, none of them can proceed.
  3. Starvation: A thread is perpetually denied access to resources because other threads are continuously prioritized over it.
  4. Context Switching: In multithreaded environments, switching between threads (context switching) introduces overhead at the OS level due to saving/restoring thread state and CPU cache invalidation.

Key Takeaways

  • Concurrency is about making progress on tasks over overlapping periods; parallelism executes work simultaneously.
  • Multithreading is a specific implementation of concurrency where a single process has multiple threads.
  • In JavaScript/Node.js, concurrency is handled natively through the event loop and async I/O.
  • In Python, you can use threading or multiprocessing for basic concurrency.

Resources to Get Started

  1. JavaScript/Node.js
  2. Python
    • Python Threading Tutorialrealpython.comReal Python tutorial on threading in Python: threads, locks, race conditions and thread pools.
    • Asynciomedium.comMedium article arguing that asyncio is an underrated tool for machine learning workloads.
  3. Concurrency and Multithreading