【Python】Implementing Concurrency (Multithreading) with the threading Module

目次

Overview

This article explains how to implement multithreading to run multiple processes concurrently using Python’s standard threading module. This is a basic technique for efficiently handling tasks that involve waiting times (I/O-bound tasks), such as API communications or file read/write operations. We will also cover how to pass arguments (args/kwargs) and background execution (daemonization).

Specifications (Input/Output)

  • Input: The function you want to run concurrently and the arguments to pass to it.
  • Output: Standard output (logs) from multiple threads and the completion of tasks through concurrent operation.
  • Requirement: Uses the Python standard library threading.

Basic Usage

The basic flow involves defining a function, creating a Thread object, starting it with start(), and waiting for it to finish with join().

import threading
import time

def task():
    time.sleep(1)
    print("Sub-thread task completed")

# Create and start the thread
t = threading.Thread(target=task)
t.start()

# Wait for the thread to end
t.join()
print("Main thread finished")

Full Code

This is an implementation example that runs a “number printing process” and a “character printing process” simultaneously. It covers both args (tuple) and kwargs (dictionary) for passing arguments.

import threading
import time

def print_numbers(thread_name, loops):
    """Function that prints numbers a specified number of times"""
    print(f"[{thread_name}] Started")
    for i in range(loops):
        print(f"{thread_name}: {i}")
        time.sleep(1) # Wait for 1 second
    print(f"[{thread_name}] Completed")

def print_letters(thread_name, chars):
    """Function that prints characters in a list sequentially"""
    print(f"[{thread_name}] Started")
    for char in chars:
        print(f"{thread_name}: {char}")
        time.sleep(1.5) # Wait for 1.5 seconds
    print(f"[{thread_name}] Completed")

if __name__ == "__main__":
    print("--- Main process started ---")

    # 1. Create threads
    # Using args: Pass arguments as a tuple (comma is required even for a single element)
    thread1 = threading.Thread(
        target=print_numbers,
        args=("Thread-Num", 3)
    )

    # Using kwargs: Pass arguments as a dictionary
    thread2 = threading.Thread(
        target=print_letters,
        kwargs={"thread_name": "Thread-Char", "chars": ["A", "B", "C"]}
    )

    # 2. Start threads (start)
    thread1.start()
    thread2.start()

    print("--- The main process continues to run while threads are executing ---")

    # 3. Wait for threads to finish (join)
    # This execution stops here until the target threads are finished
    thread1.join()
    thread2.join()

    print("--- All threads have finished ---")

Customization Points

Meaning of Parameters

These are the main arguments used when creating the threading.Thread class.

ParameterMeaning / RoleExample Setting
targetSpecifies the function object to be executed in the thread.target=my_func
argsSpecifies arguments to pass to the function as a “tuple”.args=("data", 1)
kwargsSpecifies arguments to pass to the function as a “dictionary”.kwargs={"key": "value"}
daemonSpecifies whether to make it a daemon thread (background). If True, the thread is forced to terminate when the main process ends.daemon=True

Role of Methods

MethodMeaning / Role
thread.start()Starts the thread process. It will not run unless this is called.
thread.join()Temporarily pauses (waits) the caller’s process until the thread process finishes.

Important Notes

Forgetting the Comma in args

The args parameter must be passed as a tuple. If there is only one argument, you must include a comma, like args=("hoge",). Writing args=("hoge") will result in an error.

CPU-bound Tasks

Due to Python’s design (GIL: Global Interpreter Lock), multithreading will not improve the speed of calculation-heavy (CPU-bound) tasks. Use multiprocessing for parallelizing calculation processes.

Race Conditions

If multiple threads rewrite the same variable (such as a list or global variable) at the same time, the data may lose consistency. Use threading.Lock for exclusive control as needed.

Advanced Usage

This is an example of using a daemon thread (daemon=True). It is used for tasks that run indefinitely, such as periodic monitoring, where you want the task to terminate automatically when the main program ends.

import threading
import time

def background_task():
    """Simulating an endless monitoring task"""
    while True:
        print("[Daemon] Monitoring...")
        time.sleep(0.5)

if __name__ == "__main__":
    # Set daemon=True
    t = threading.Thread(target=background_task, daemon=True)
    t.start()

    print("Main process: Will run for only 2 seconds")
    time.sleep(2)
    
    print("Main process: Finished")
    # When the main process ends here, background_task is also forced to stop
    # (The key is NOT calling join())

Summary

The threading module allows you to increase the overall efficiency of your programs by making good use of time spent waiting for communications or other tasks. The basic flow is specifying the function with target, starting with start(), and waiting with join(). Use daemon=True only when you want the task to continue running in the background.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次