Python > Modules and Packages > Standard Library > Concurrency and Parallelism (`threading`, `multiprocessing` modules)
Basic Threading Example
This code snippet demonstrates a basic example of using the threading
module to run a function concurrently. It creates two threads that execute the print_numbers
function, printing numbers from 1 to 5 with a slight delay. This highlights how to create, start, and join threads to manage concurrent execution.
Code
The threading
module allows you to run multiple functions concurrently. We define a function print_numbers
that simulates work by sleeping for a short period before printing a number. We create two threads, each running this function. thread.start()
begins execution, and thread.join()
ensures the main program waits for each thread to complete before exiting. The args
parameter is a tuple passed as arguments to the target function.
import threading
import time
def print_numbers(thread_id):
for i in range(1, 6):
time.sleep(0.2) # Simulate some work
print(f"Thread {thread_id}: {i}")
if __name__ == "__main__":
# Create two threads
thread1 = threading.Thread(target=print_numbers, args=(1,))
thread2 = threading.Thread(target=print_numbers, args=(2,))
# Start the threads
thread1.start()
thread2.start()
# Wait for the threads to finish
thread1.join()
thread2.join()
print("All threads finished.")
Concepts Behind the Snippet
multiprocessing
is often preferred for CPU-bound tasks in Python.threading.Thread
: This class is used to create new threads. You specify the target function (the function to be executed in the thread) and any arguments to pass to that function.thread.start()
: This method starts the thread's execution.thread.join()
: This method blocks the calling thread (in this case, the main thread) until the thread whose join()
method is called completes its execution. It's crucial for ensuring your main program doesn't exit before the threads are done.
Real-Life Use Case
Imagine a web server that needs to handle multiple client requests simultaneously. Instead of processing each request sequentially, the server can create a new thread for each request. This allows the server to handle multiple requests concurrently, improving its responsiveness. Similarly, in GUI applications, threads can be used to perform long-running tasks (like network operations or complex calculations) without freezing the user interface.
Best Practices
threading.Lock
) to protect shared resources.queue
module) to pass data between threads. Queues provide a thread-safe way to communicate.multiprocessing
module.
Interview Tip
Be prepared to discuss the difference between threads and processes, the advantages and disadvantages of using threads, and the implications of the GIL in Python. Explain how you would handle concurrency issues such as race conditions and deadlocks. Also, understand the use cases for both threading and multiprocessing.
When to Use Threading
Threading is well-suited for I/O-bound tasks where the program spends most of its time waiting for external operations to complete (e.g., network requests, file I/O). The GIL has less impact in these scenarios because threads are often waiting for I/O rather than executing CPU-intensive code.
Memory Footprint
Threads generally have a smaller memory footprint compared to processes because they share the same memory space. However, sharing memory also increases the risk of concurrency issues, so it's important to manage shared resources carefully.
Alternatives
multiprocessing
: For CPU-bound tasks that require true parallelism, multiprocessing
is often a better choice than threading
.asyncio
: For asynchronous programming and event-driven concurrency, asyncio
provides a powerful framework for handling I/O-bound tasks efficiently.
Pros
Cons
FAQ
-
What is the GIL in Python, and how does it affect threading?
The Global Interpreter Lock (GIL) is a mutex that allows only one thread to hold control of the Python interpreter at any one time. This means that only one thread can execute Python bytecode at a time, even on multi-core processors. The GIL limits true parallelism for CPU-bound tasks but has less impact on I/O-bound tasks where threads spend most of their time waiting for external operations. -
How can I avoid race conditions when using threads?
To avoid race conditions, you need to protect shared resources using locking mechanisms. Thethreading.Lock
class provides a simple mutex lock. You can acquire the lock before accessing a shared resource and release it after you are done. Alternatively, consider using thread-safe data structures like queues to pass data between threads. -
When should I use
multiprocessing
instead ofthreading
?
You should usemultiprocessing
when you need true parallelism for CPU-bound tasks. Since each process has its own interpreter, the GIL limitation doesn't apply. However,multiprocessing
has a higher overhead thanthreading
due to the need to create and manage separate processes.