Multithreading
Multithreading allows a program to run several threads concurrently. The threading module provides a high-level interface.
In the real world, many applications need to do several tasks at once. Imagine a chat app that must receive new messages while still responding to user input — without multithreading, the application will be blocked while waiting for data to arrive. With multithreading, heavy tasks like file downloads, database connections, or data processing can run in the background without disturbing the user interface.
Python uses the GIL (Global Interpreter Lock), which limits thread execution within a single interpreter. For I/O-bound tasks (waiting on network, disk, database), multithreading remains very effective because the GIL is released while I/O operations are in progress. However, for CPU-bound tasks (heavy computation), consider multiprocessing or concurrent.futures as alternatives.
Keep in mind that threads share the same memory. This makes data exchange between threads easy, but it also requires synchronization with Lock or RLock to avoid race conditions when multiple threads access the same variable. Always use synchronization mechanisms when shared data is accessed by more than one thread.
Code Example
import threading
import time
def task(name, seconds):
print(f"{name} started...")
time.sleep(seconds)
print(f"{name} done!")
# Creating and running threads
t1 = threading.Thread(target=task, args=("Thread-1", 2))
t2 = threading.Thread(target=task, args=("Thread-2", 3))
t1.start()
t2.start()
t1.join() # wait for completion
t2.join()
print("All threads done!")
Code Explanation
First, import threading and import time are used to import the needed modules. The task(name, seconds) function is defined as the target that each thread will run — it prints a start message, waits for seconds seconds, then prints a completion message.
Two threads are created with threading.Thread(), each with different arguments: Thread-1 sleeps for 2 seconds, Thread-2 sleeps for 3 seconds. The .start() method runs the threads concurrently — both functions start almost simultaneously without waiting for each other.
The .join() method forces the main program to wait for each thread to finish before proceeding to the next line. Without join(), the main program would immediately print "All threads done!" without waiting for the threads to finish their work.
Everyday Example
A file download application fetches from several servers at once. For example, the application wants to fetch 5 PDF files from different servers. Without threading, the program downloads them one by one — if each file takes 3 seconds, the total is 15 seconds. With threading, all files are downloaded simultaneously — the total time is just 3 seconds (the longest one). This dramatically improves speed for tasks that wait on external responses such as HTTP requests, database queries, or file reads/writes.
Code example: create a list of URLs, loop to create a thread per URL, run .start(), then .join() all of them. Make sure to use a Lock if each thread needs to write results to the same file.
Summary
Multithreading is an essential technique for concurrent programming in Python. It is best suited for I/O-bound tasks such as network, file, and database work. Remember the GIL limitation, use synchronization (Lock) when accessing shared data, and consider multiprocessing for heavy CPU loads. Used properly, multithreading makes applications much more responsive and efficient.