Python Multithreading with Examples

Introduction

Modern applications often need to perform multiple tasks simultaneously. For example, a web browser can download files while displaying web pages, and a chat application can send and receive messages at the same time.

Python provides Multithreading, a technique that allows multiple threads to run within a single process. Threads share the same memory space and can execute tasks concurrently, making them especially useful for I/O-bound operations such as file handling, network communication, and database access.

Python’s built-in threading module makes it easy to create and manage threads. By using multithreading, developers can build faster and more responsive applications.

What is Multithreading?

Multithreading is the process of executing multiple threads within a single process.

A thread is the smallest unit of execution in a program.

Instead of executing tasks one after another:


Task 1
↓
Task 2
↓
Task 3

Multithreading allows Task 1, Task 2, and Task 3 to run concurrently.

This improves application responsiveness and resource utilization.

Why Use Multithreading?

Multithreading offers several advantages:

1. Faster Execution

Multiple tasks can progress simultaneously.

2. Improved User Experience

Applications remain responsive while background tasks execute.

3. Better Resource Utilization

Threads share memory efficiently.

4. Simplified Concurrent Programming

Allows handling multiple operations within a single application.

5. Ideal for I/O Operations

Useful for:

  • File handling
  • Database access
  • API calls
  • Web scraping
  • Downloads

Process vs Thread

Feature Process Thread
Memory Separate Shared
Creation Cost High Low
Speed Slower Faster
Communication Complex Easy
Resource Usage Higher Lower

Python Threading Module

Python provides the built-in threading module.

Import it:


import threading

This module contains classes and methods for creating and managing threads.

Creating a Thread

Syntax


thread = threading.Thread(
    target=function_name
)

Start the thread:


thread.start()

Example: Simple Thread


import threading
def task():
    print("Thread Running")
thread = threading.Thread(
    target=task
)
thread.start()

Output:

Thread Running

Example: Multiple Threads


import threading
def task():
    print("Task Executed")
thread1 = threading.Thread(
    target=task
)
thread2 = threading.Thread(
    target=task
)
thread1.start()
thread2.start()

Output:

Task Executed
Task Executed

Multiple threads run concurrently.

Passing Arguments to Threads

You can pass parameters using the args argument.

Example:


import threading
def greet(name):
    print(
        f"Hello {name}"
    )
thread = threading.Thread(
    target=greet,
    args=("John",)
)
thread.start()

Output:

Hello John

Using join()

The join() method waits for a thread to finish execution.

Example:


import threading
import time
def task():
    time.sleep("2")
    print("Task Completed")
thread = threading.Thread(
    target=task
)
thread.start()
thread.join()
print("Program Finished")

Output:

Task Completed
Program Finished

Current Thread Information

Example:


import threading
print(
    threading.current_thread()
)

Output:

<_MainThread(MainThread)>

Naming Threads


import threading
def task():
    print(
        threading.current_thread().name
    )
thread = threading.Thread(
    target=task,
    name="WorkerThread"
)
thread.start()

Output:

WorkerThread

Creating Threads Using Thread Class

You can extend the Thread class.

Example:


import threading
class MyThread(
    threading.Thread
):
    def run(self):
        print(
            "Custom Thread Running"
        )
thread = MyThread()
thread.start()

Output:

Custom Thread Running

Daemon Threads

Daemon threads run in the background.

Example:


import threading
def background_task():
    while True:
        pass
thread = threading.Thread(
    target=background_task,
    daemon=True
)
thread.start()

Daemon threads terminate automatically when the main program exits.

Multithreading Example: Countdown Timer


import threading
import time
def countdown():
    for i in range(5, 0, -1):
        print(i)
        time.sleep("1")
thread = threading.Thread(
    target=countdown
)
thread.start()

Output:

5
4
3
2
1

Multithreading Example: File Download Simulation


import threading
import time
def download(file):
    print(
        f"Downloading {file}"
    )
    time.sleep("2")
    print(
        f"{file} Downloaded"
    )
files = [
    "file1.zip",
    "file2.zip",
    "file3.zip"
]
#for file in files:
  thread = threading.Thread(
        target=download,
        args=(file,)
    )
    thread.start()

Output:

Downloading file1.zip
Downloading file2.zip
Downloading file3.zip

Downloads occur concurrently.

Multithreading Example: Web Scraping


import threading
def scrape(url):
    print(
        f"Scraping {url}"
    )
urls = [
    "site1.com",
    "site2.com",
    "site3.com"
]
#for url in urls:
 thread = threading.Thread(
        target=scrape,
        args=(url,)
    )
    thread.start()

This speeds up data collection.

Multithreading Example: Sending Emails


import threading
def send_email(user):
    print(
        f"Email Sent To {user}"
    )
users = [
    "John",
    "Mike",
    "Sara"
]
#for user in users:
    threading.Thread(
        target=send_email,
        args=(user,)
    ).start()

Output:

Email Sent To John
Email Sent To Mike
Email Sent To Sara

Thread Synchronization

When multiple threads share resources, synchronization is required.

Example:


counter = 0

Multiple threads updating the same variable may cause race conditions.

Race Condition Example


import threading
counter = 0
def increment():
    global counter
    for i in range(1000):
        counter += 1

Results may become inconsistent.

Using Lock for Synchronization


import threading
lock = threading.Lock()
counter = 0
def increment():
    global counter
    for i in range(1000):
        with lock:
            counter += 1

Locks prevent race conditions.

Python GIL and Multithreading

Python uses a mechanism called the Global Interpreter Lock (GIL).

The GIL allows only one thread to execute Python bytecode at a time.

Because of GIL:

  • CPU-bound tasks gain little benefit from threads.
  • I/O-bound tasks benefit significantly.

Examples of I/O-bound tasks:

  • File operations
  • Database queries
  • API requests
  • Network communication

Common Thread Methods

Method Purpose
start() Starts a thread
join() Waits for completion
is_alive() Checks if thread is running
current_thread() Returns current thread
Lock() Creates a lock
RLock() Creates a reentrant lock

Advantages of Multithreading

Advantage Description
Faster I/O Operations Improves responsiveness
Shared Memory Easy communication
Lower Resource Usage Efficient execution
Better User Experience Prevents application freezing
Concurrent Execution Handles multiple tasks

Common Mistakes

1. Forgetting start()

Incorrect:


thread = threading.Thread(
    target=task
)

Correct:


thread.start()

2. Ignoring join()

Threads may not complete before the program exits.

Use:


thread.join()

3. Not Using Locks

Shared resources can cause race conditions.

4. Creating Too Many Threads

Excessive threads can reduce performance.

5. Using Threads for CPU-Bound Tasks

For heavy computation, use multiprocessing instead.

Conclusion

Python Multithreading is a powerful technique that enables multiple tasks to run concurrently within a single process.

Multithreading is especially beneficial for I/O-bound tasks such as web scraping, file processing, downloads, and network communication. Although Python’s Global Interpreter Lock (GIL) limits performance improvements for CPU-intensive tasks, multithreading remains an essential tool for modern Python development.

Related Python Tutorials