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.
| Parameter | Meaning / Role | Example Setting |
| target | Specifies the function object to be executed in the thread. | target=my_func |
| args | Specifies arguments to pass to the function as a “tuple”. | args=("data", 1) |
| kwargs | Specifies arguments to pass to the function as a “dictionary”. | kwargs={"key": "value"} |
| daemon | Specifies 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
| Method | Meaning / 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.
