Overview
This article explains how to implement multithreading by inheriting (subclassing) the Thread class in the Python threading module. Compared to simply passing a function to the target argument, this approach is effective when you want the thread to hold its own data (state) or when you want to encapsulate complex logic within a class to improve code readability.
Specifications (Input/Output)
- Input: Thread-specific names or parameters (during initialization).
- Output: Concurrent execution logs from each thread.
- Requirement: Inherit from
threading.Threadand override therun()method.
Basic Usage
Create a class that inherits from threading.Thread and write the processing logic inside the run() method.
import threading
class MyThread(threading.Thread):
def run(self):
# This method is called in a separate thread when start() is executed
print("Thread processing is running")
t = MyThread()
t.start() # Do not call run() directly; always call start()
t.join()
Full Code
Below is a complete implementation example where we define separate classes for a “Number Printing Thread” and a “Character Printing Thread.” We pass names during __init__ and store them as instance variables.
import threading
import time
class NumberPrinterThread(threading.Thread):
"""Thread class that prints numbers in a count-up fashion"""
def __init__(self, thread_name, loops=5):
# Always initialize the parent class (Thread) first
super().__init__()
self.thread_name = thread_name
self.loops = loops
def run(self):
"""Main process executed by the thread"""
print(f"[{self.thread_name}] Started")
for i in range(self.loops):
print(f"{self.thread_name}: {i}")
time.sleep(1)
print(f"[{self.thread_name}] Completed")
class LetterPrinterThread(threading.Thread):
"""Thread class that prints characters from a list sequentially"""
def __init__(self, thread_name, chars=None):
super().__init__()
self.thread_name = thread_name
# Set default arguments
self.chars = chars if chars else ["A", "B", "C", "D", "E"]
def run(self):
print(f"[{self.thread_name}] Started")
for letter in self.chars:
print(f"{self.thread_name}: {letter}")
time.sleep(1.5)
print(f"[{self.thread_name}] Completed")
if __name__ == "__main__":
# Create instances of each class
thread1 = NumberPrinterThread("Num-Thread")
thread2 = LetterPrinterThread("Char-Thread")
# Start the threads
# Calling start() executes the run() method internally in a separate thread
thread1.start()
thread2.start()
# Wait for completion
thread1.join()
thread2.join()
print("All processes have finished")
Customization Points
Mandatory Rules for Subclassing
Overriding the run() method
Describe the process to be executed when the thread starts here. Only the code written in this method runs in the separate thread.
Calling super().init() in init()
If you define a constructor (initialization method), you must call super().__init__() at the very beginning. If you forget this, thread functions (like start() or the name attribute) will not initialize correctly, leading to errors.
Benefits
- Data Persistence: It becomes easy to manage thread-specific data as instance variables, such as
self.data. - Structural Clarity: You can divide complex processing into methods within the class, making large-scale thread management easier to understand.
Important Notes
Do not call run() directly
If you call the .run() method directly on an instance, a new thread will not be created. Instead, the code will run synchronously on the main thread. Always call the .start() method.
Thread Safety
Using a class does not automatically make access to shared resources safe. If multiple threads modify the same object attributes, you must use exclusive control, such as threading.Lock.
Advanced Usage
This is an advanced example where the thread has a stop flag so it can be safely terminated from the outside. Classifying the thread allows for clean management of such “control states.”
import threading
import time
class StoppableThread(threading.Thread):
def __init__(self):
super().__init__()
self._stop_event = threading.Event()
def stop(self):
"""Method to stop the thread from the outside"""
self._stop_event.set()
def run(self):
print("Starting process...")
while not self._stop_event.is_set():
print("Running...")
time.sleep(1)
# Write your main logic here
print("Stop instruction received. Terminating.")
# Usage Example
if __name__ == "__main__":
t = StoppableThread()
t.start()
time.sleep(3)
t.stop() # Stop from outside
t.join()
Summary
The style of inheriting threading.Thread is an object-oriented approach suitable for when you want the thread to have its own states (variables) and methods (operations). The Pythonic design is to choose based on the task: use a function (target=func) for simple tasks and class inheritance for complex ones.
