Home » Python Threading Tutorial with Examples

Python Threading Tutorial with Examples

Threading is a method in Python that allows you to run multiple operations concurrently in the same program.

It is particularly useful when you need to perform tasks that are I/O-bound, such as reading/writing files, network operations, or even performing tasks while keeping the main application responsive.

In this tutorial, you will learn:

What are threads?
Creating threads using threading module
Starting multiple threads
Using join() to wait for threads to complete
Passing arguments to threads
Using thread locks to avoid race conditions
Practical examples of threading in Python

1. What Are Threads?

A thread is a lightweight subprocess, the smallest unit of processing. Each thread runs independently, but shares memory and other resources with the main thread and other threads.

Python has a built-in threading module that makes working with threads easy.

However, Python’s Global Interpreter Lock (GIL) means that threads in Python are not truly concurrent when it comes to CPU-bound tasks.

For I/O-bound tasks, threading can significantly improve the performance.

2. Creating Threads Using threading Module

To create a thread, you can use Python’s threading module, which provides a higher-level interface for working with threads. The Thread class in this module allows you to create a new thread.

Example 1: Basic Thread Creation

import threading
import time

def print_numbers():
    for i in range(1, 6):
        print(f"Number: {i}")
        time.sleep(1)

# Creating a thread
thread = threading.Thread(target=print_numbers)

# Starting the thread
thread.start()

# Main program continues to run while the thread runs concurrently
print("Main thread is still running")

Output:

Main thread is still running
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

In this example, we create a thread using threading.Thread(), where the target is the function print_numbers().

When we call thread.start(), the new thread starts and runs concurrently with the main program.

3. Starting Multiple Threads

You can start multiple threads to run different tasks simultaneously.

Example 2: Starting Multiple Threads

import threading
import time

def print_numbers():
    for i in range(1, 4):
        print(f"Number: {i}")
        time.sleep(1)

def print_letters():
    for letter in ['A', 'B', 'C']:
        print(f"Letter: {letter}")
        time.sleep(1)

# Creating multiple threads
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# Starting threads
thread1.start()
thread2.start()

# Both threads run concurrently
print("Main thread continues to run")

Output (the output order of numbers and letters may vary):

Main thread continues to run
Number: 1
Letter: A
Number: 2
Letter: B
Number: 3
Letter: C

In this example, both thread1 and thread2 run simultaneously. The order of output may vary since they are running concurrently.

4. Using join() to Wait for Threads to Complete

If you want the main thread to wait for other threads to finish before continuing, you can use the join() method.

Example 3: Waiting for Threads to Complete with join()

import threading
import time

def print_numbers():
    for i in range(1, 4):
        print(f"Number: {i}")
        time.sleep(1)

def print_letters():
    for letter in ['A', 'B', 'C']:
        print(f"Letter: {letter}")
        time.sleep(1)

# Creating threads
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# Starting threads
thread1.start()
thread2.start()

# Wait for both threads to finish
thread1.join()
thread2.join()

print("Both threads have completed")

Output:

Number: 1
Letter: A
Number: 2
Letter: B
Number: 3
Letter: C
Both threads have completed

Here, join() ensures that the main thread waits for both thread1 and thread2 to finish before printing “Both threads have completed.”

5. Passing Arguments to Threads

If your thread function needs arguments, you can pass them using the args parameter in the Thread constructor.

Example 4: Passing Arguments to Threads

import threading
import time

def greet(name, count):
    for i in range(count):
        print(f"Hello, {name}!")
        time.sleep(1)

# Passing arguments to threads
thread = threading.Thread(target=greet, args=("Alice", 3))

# Starting the thread
thread.start()

# Waiting for the thread to complete
thread.join()

print("Greeting thread has finished")

Output:

Hello, Alice!
Hello, Alice!
Hello, Alice!
Greeting thread has finished

In this example, we pass arguments “Alice” and 3 to the greet function using the args parameter.

6. Using Thread Locks to Avoid Race Conditions

When multiple threads modify shared data, it can lead to race conditions, which occur when threads try to access shared resources at the same time.

This can lead to unpredictable or incorrect results. To avoid race conditions, you can use locks.

A lock ensures that only one thread accesses a piece of code or data at a time.

Example 5: Using Locks to Avoid Race Conditions

import threading

balance = 0
lock = threading.Lock()

def deposit(amount):
    global balance
    for _ in range(10000):
        with lock:
            balance += amount

def withdraw(amount):
    global balance
    for _ in range(10000):
        with lock:
            balance -= amount

# Creating threads
thread1 = threading.Thread(target=deposit, args=(1,))
thread2 = threading.Thread(target=withdraw, args=(1,))

# Starting threads
thread1.start()
thread2.start()

# Waiting for both threads to finish
thread1.join()
thread2.join()

print(f"Final balance: {balance}")

Output:

Final balance: 0

In this example, without the lock, both threads could try to modify the balance variable simultaneously, leading to incorrect results.

By using lock, only one thread can modify balance at a time, ensuring that the final balance is correct.

7. Practical Examples of Threading in Python

Example 1: Web Scraping with Threads

Let’s say you want to download content from multiple URLs at the same time. Using threads can significantly speed up the process.

import threading
import requests

def fetch_url(url):
    response = requests.get(url)
    print(f"Fetched {url} with status: {response.status_code}")

# List of URLs to fetch
urls = [
    "https://www.example.com",
    "https://www.python.org",
    "https://www.github.com"
]

# Creating threads for each URL
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]

# Starting all threads
for thread in threads:
    thread.start()

# Waiting for all threads to complete
for thread in threads:
    thread.join()

print("All URLs fetched")

In this example, each URL is fetched in its own thread, speeding up the downloading process by running concurrently.

Example 2: Simulating Background Tasks

Imagine you want to simulate background tasks like saving data while your main program continues to run.

import threading
import time

def background_task():
    print("Starting background task...")
    time.sleep(5)
    print("Background task completed!")

# Starting the background task
thread = threading.Thread(target=background_task)
thread.start()

# Main program continues to run
print("Main program continues to run while the background task runs...")

# Waiting for the background task to finish
thread.join()

print("Background task has finished")

Output:

Starting background task...
Main program continues to run while the background task runs...
Background task completed!
Background task has finished

In this example, the main program runs while the background task (which simulates a long-running process) executes in a separate thread.

Conclusion

Threads are an essential tool for improving the performance and responsiveness of Python programs, especially when performing I/O-bound tasks. Here’s a summary of what you’ve learned:

How to create and start threads using the threading module.
How to use multiple threads to run tasks concurrently.
Using join() to wait for threads to finish.
Passing arguments to threads.
Preventing race conditions by using thread locks.
Practical examples like web scraping and background tasks.

With these concepts, you’re ready to start using threads to make your Python programs faster and more efficient!

You may also like

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More