Python Threading: Threads, Locks, Concurrency

Updated on: August 20, 2026
Reading time: 4 minutes
Problemas de travamento com threading em scripts Python

Have you ever run a script and suddenly had the interface stop responding or the terminal appear frozen? This is extremely common among developers moving beyond the basics. By default, Python executes instructions sequentially: one after another. If a task takes a long time (downloading a large file, processing thousands of records), everything else must wait. This is where Python threading comes in, a technique for running multiple tasks simultaneously and preventing your applications from locking up.

Why does your script freeze?

Imagine following a cake recipe: you mix the batter, put it in the oven, and then stand in front of the oven for 40 minutes, unable to wash dishes or set the table. In programming, this is called synchronous or sequential execution. When a script needs to read a large file or make a web request, it sends the request and “blocks,” waiting for the response. During that wait, the processor is idle, but your program appears frozen to the user.

What is threading in Python?

A “thread” is the smallest unit of processing that can be managed by the operating system. Python threading lets your program execute multiple threads within the same process. Back to the kitchen: while the cake is baking, you can start washing the dishes. You are still one person (one process), but managing multiple tasks by rapidly alternating between them.

Threading is different from multiprocessing. Threading shares the same memory space; multiprocessing uses multiple CPU cores truly in parallel. Python has the Global Interpreter Lock (GIL), which ensures only one thread executes Python code at a time. This sounds like a limitation, but for tasks involving waiting (network, disk), threading is highly efficient. For a deep dive on the GIL, see Python GIL explained.

Practical example: simulating downloads without freezing

import threading
import time

def download_file(name):
    print(f"Starting download of {name}...")
    time.sleep(3)  # Simulating a slow task
    print(f"Download of {name} complete!")

# Create threads
t1 = threading.Thread(target=download_file, args=("Report.pdf",))
t2 = threading.Thread(target=download_file, args=("Image.png",))

# Start threads
t1.start()
t2.start()

print("Main program continues running!")

# Wait for threads to finish
t1.join()
t2.join()
print("All downloads complete.")

The .join() method tells the main program: “wait here until this thread finishes before continuing.” Without it, the script might exit before the downloads complete. The official Python threading documentation covers all available classes and methods.

Preventing GUI freezes

One of the biggest uses of threading is in GUI applications. If you run a heavy function directly on a button click in Tkinter, the window’s MainLoop pauses and the window shows “Not Responding.” Always fire heavy logic in a separate thread so the MainLoop keeps running smoothly while processing happens in the background.

Race conditions and Lock

When multiple threads try to modify the same variable simultaneously, a race condition can corrupt data. Use a Lock object to prevent this: when one thread is accessing shared data, it “locks” access so other threads must wait.

import threading

lock = threading.Lock()
counter = 0

def increment():
    global counter
    with lock:           # Only one thread enters this block at a time
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(1000)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"Final counter: {counter}")  # Always 1000

ThreadPoolExecutor: the cleaner approach

For managing many threads, use concurrent.futures.ThreadPoolExecutor instead of creating threads manually. It manages a pool of threads for you, reusing them and handling cleanup automatically:

from concurrent.futures import ThreadPoolExecutor

def process_item(item):
    # Simulate work
    return item * 2

items = [1, 2, 3, 4, 5]

with ThreadPoolExecutor(max_workers=3) as executor:
    results = list(executor.map(process_item, items))

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

Best practices summary

  • Use ThreadPoolExecutor for managing multiple threads instead of manual creation.
  • Daemon threads: Set daemon=True for background monitor threads that should die when the main program exits.
  • Avoid CPU-bound tasks: For heavy calculations, threading does not help due to the GIL. Use multiprocessing or NumPy instead.
  • Use logging over print: Python’s logging module is thread-safe and identifies which thread produced each message.

Frequently asked questions

Does threading make Python faster?

For I/O-bound tasks (waiting), yes. For CPU-bound tasks (calculations), no, due to the GIL.

What is the difference between start() and run()?

start() launches the function in a new thread. run() executes the function in the current thread sequentially.

What happens if I forget join()?

The main program continues its execution. Non-daemon threads will keep the process alive until they finish.

How do I stop a thread forcefully?

The threading module does not provide a safe way to kill a thread. Use a control variable (flag) that the thread itself checks to exit the loop.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    TOML configuration in a Python project
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python tomllib: Read TOML Files

    Learn how to read TOML with Python tomllib, validate settings, handle parse errors, and organize projects with pyproject.toml.

    Ler mais

    Tempo de leitura: 6 minutos
    24/07/2026
    Developer monitoring structured Python application logs
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Observability in Python with structlog

    Build practical Python observability with structlog, JSON events, request context, tests, performance controls, and deployment guidance.

    Ler mais

    Tempo de leitura: 5 minutos
    24/07/2026
    Secure Python application configuration with Pydantic Settings
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Pydantic Settings for Safe Config

    Learn how to validate environment variables, manage secrets, and organize Python application settings with Pydantic Settings.

    Ler mais

    Tempo de leitura: 5 minutos
    23/07/2026
    Desenvolvedor programando em Python com Ruff para lint e formatação
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Ruff: Lint and Format Python Code

    Learn Ruff for Python linting, formatting, automatic fixes, pyproject.toml, VS Code and CI with a practical project configuration.

    Ler mais

    Tempo de leitura: 9 minutos
    22/07/2026
    2 balões de comentários em um fundo laranja
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Comments: Syntax and Best Practices

    Learn Python comments with single-line and inline examples, block explanations, TODO notes, docstrings, security and performance context, review standards, and

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026
    Pessoa programando em um notebook, vista de trás, com código desfocado exibido na tela em um fundo escuro
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Docstrings: Functions, Classes, and Modules

    Learn Python docstrings for functions, classes, methods, modules, parameters, returns, exceptions, examples, pydoc, conventions, and documentation tools.

    Ler mais

    Tempo de leitura: 6 minutos
    10/07/2026