Hone logo
Hone
Problems

Thread Pool Implementation in Python

Thread pools are a powerful tool for managing concurrent tasks, especially when dealing with I/O-bound operations. This challenge asks you to implement a basic thread pool in Python, allowing you to efficiently execute a set of tasks concurrently without the overhead of creating and destroying threads for each task. A well-implemented thread pool can significantly improve performance in applications that involve numerous short-lived tasks.

Problem Description

You are tasked with creating a ThreadPool class in Python. This class should manage a pool of worker threads and distribute tasks to them. The ThreadPool should have the following functionalities:

  • Initialization: The constructor should accept an integer num_threads representing the number of worker threads to create in the pool.
  • Task Submission: A submit(task) method should accept a callable task (a function or any object with a __call__ method) and add it to the pool's queue for execution.
  • Task Execution: The worker threads should continuously monitor the task queue and execute tasks as they become available.
  • Shutdown: A shutdown() method should gracefully terminate the thread pool. This method should prevent new tasks from being submitted and wait for all currently executing tasks to complete before exiting.
  • Thread Safety: The task queue should be thread-safe to prevent race conditions when multiple threads are accessing it.

Key Requirements:

  • Use the threading module for thread management.
  • Use a queue.Queue object to manage the tasks.
  • Ensure that the thread pool can handle an arbitrary number of tasks.
  • The shutdown() method should block until all tasks are completed.

Expected Behavior:

When a task is submitted using submit(), it should be added to the queue. A worker thread will pick up the task from the queue and execute it. The shutdown() method should wait for all tasks in the queue to be processed before returning.

Edge Cases to Consider:

  • What happens if num_threads is 0 or negative? (Should raise a ValueError)
  • What happens if the task raises an exception during execution? (The exception should be propagated to the caller of submit())
  • What happens if shutdown() is called multiple times? (Should not cause errors, but should not accept new tasks)
  • What happens if the task queue is empty when shutdown() is called? (Should return immediately)

Examples

Example 1:

Input:
num_threads = 2
tasks = [lambda: print("Task 1"), lambda: print("Task 2"), lambda: print("Task 3")]

pool = ThreadPool(num_threads)
for task in tasks:
    pool.submit(task)
pool.shutdown()

Output: (Order may vary due to concurrency)

Task 1
Task 2
Task 3

Explanation: The thread pool creates 2 threads. The tasks are submitted to the queue and executed by the threads. Finally, shutdown() waits for all tasks to complete before returning.

Example 2:

Input:
num_threads = 3
tasks = [lambda: print("Task 1"), lambda: raise ValueError("Simulated Error"), lambda: print("Task 3")]

pool = ThreadPool(num_threads)
try:
    for task in tasks:
        pool.submit(task)
    pool.shutdown()
except ValueError as e:
    print(f"Caught exception: {e}")

Output:

Task 1
Caught exception: Simulated Error

Explanation: The first task executes successfully. The second task raises a ValueError, which is caught by the try...except block. The third task is not executed because the exception is caught.

Example 3: (Edge Case - Zero Threads)

Input:
num_threads = 0
tasks = [lambda: print("Task 1")]

pool = ThreadPool(num_threads) # Should raise ValueError

Output:

ValueError: Number of threads must be a positive integer.

Explanation: Attempting to create a thread pool with zero threads raises a ValueError.

Constraints

  • num_threads must be a positive integer. A ValueError should be raised if it is not.
  • The submit() method should not block. It should add the task to the queue and return immediately.
  • The shutdown() method should block until all tasks are completed.
  • The task queue should be thread-safe.
  • The implementation should be reasonably efficient, avoiding unnecessary overhead.

Notes

  • Consider using a queue.Queue object for thread-safe task management.
  • Worker threads should continuously monitor the queue for new tasks.
  • The shutdown() method should set a flag to prevent new tasks from being submitted and then join all worker threads to wait for them to finish.
  • Think about how to handle exceptions raised by tasks. You might want to propagate them to the caller of submit().
  • This is a simplified thread pool implementation. Production-ready thread pools often include features like task prioritization, timeouts, and more sophisticated error handling.
Loading editor...
python