Python Threading: Stop Your Script From Freezing

Updated on: June 3, 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

    Dicas para otimizar scripts Python lentos
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Why Is Your Python Script Slow? Fixes

    Learn why Python scripts run slowly and how to fix it: use built-ins, list comprehensions, sets, generators, NumPy, cProfile profiling,

    Ler mais

    Tempo de leitura: 3 minutos
    03/06/2026
    Criação de cliente TCP simples usando Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Build a Simple Python TCP Client

    Build a simple Python TCP client using the socket module: connect to a server, send and receive bytes, handle errors,

    Ler mais

    Tempo de leitura: 3 minutos
    03/06/2026
    Criação de hashes seguros para senhas usando Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Password Hashing with bcrypt

    Learn Python password hashing with bcrypt: generate secure hashes, verify passwords, understand salt, and build a complete authentication system.

    Ler mais

    Tempo de leitura: 4 minutos
    03/06/2026
    Uso de dataclasses para simplificar classes em Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python Dataclasses: Clean Data Classes Easily

    Learn how Python dataclasses work: auto-generate __init__, __repr__, __eq__, use default values, field(), frozen=True, __post_init__, and asdict/astuple.

    Ler mais

    Tempo de leitura: 4 minutos
    03/06/2026
    Entendendo como o GIL afeta performance em Python
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Python GIL: What It Is and How It Affects Code

    Learn what Python's GIL is, why it exists, how it limits multithreading, and when to use multiprocessing to achieve true

    Ler mais

    Tempo de leitura: 6 minutos
    03/06/2026
    Medição de tempo de execução de código Python com timeit
    Best Practices
    Foto de perfil de Leandro Hirt da Academify

    Measure Python Code Speed with timeit

    Learn how to measure Python code execution time with timeit: command line, setup parameter, function references, repeat(), and benchmarking best

    Ler mais

    Tempo de leitura: 4 minutos
    30/05/2026