Hone logo
Hone
Problems

Concurrent Task Processing with a Worker Pool in Go

Worker pools are a powerful concurrency pattern that allows you to efficiently manage a set of worker goroutines to process tasks concurrently. This challenge asks you to implement a worker pool in Go, enabling you to distribute tasks across a fixed number of workers, maximizing resource utilization and improving performance for computationally intensive operations. This is a common pattern for handling background jobs, image processing, or any task that can be broken down into smaller, independent units of work.

Problem Description

You are tasked with creating a WorkerPool type in Go that manages a pool of worker goroutines. The pool should accept a number of workers and a task queue. Each worker should continuously retrieve tasks from the queue and execute them. When the queue is empty and all workers have finished their current tasks, the worker pool should gracefully shut down.

Key Requirements:

  • Initialization: The WorkerPool should be initialized with a specified number of workers and a channel for receiving tasks.
  • Task Processing: Each worker should continuously listen on the task channel, receive a task, process it, and then return to listening for more tasks.
  • Graceful Shutdown: The worker pool should provide a mechanism to signal workers to stop processing tasks and exit gracefully. This should be achieved by sending a "stop" signal to the task channel.
  • Error Handling: The task processing function should handle potential errors during task execution. Errors should be logged (you can use fmt.Println for simplicity in this challenge).
  • Task Type: Tasks will be represented by a function that takes no arguments and returns an error. This allows for maximum flexibility in the types of tasks the pool can handle.

Expected Behavior:

  1. When a task is sent to the task channel, one of the available workers should pick it up and execute it.
  2. Workers should continue processing tasks until a "stop" signal is sent to the task channel.
  3. After receiving the "stop" signal, workers should finish their current task (if any) and then exit.
  4. The worker pool should not panic or deadlock.

Edge Cases to Consider:

  • What happens if the task channel is closed before any workers are started?
  • What happens if a worker encounters an error while processing a task?
  • What happens if more tasks are sent to the channel than there are workers?
  • What happens if the number of workers is zero?

Examples

Example 1:

Input: numWorkers = 2, tasks = [func() error { fmt.Println("Task 1"); return nil }, func() error { fmt.Println("Task 2"); return nil }, func() error { fmt.Println("Task 3"); return nil }]
Output: (printed to console)
Task 1
Task 2
Task 3
Explanation: Two workers process the three tasks concurrently. The order of execution might vary.

Example 2:

Input: numWorkers = 3, tasks = [func() error { fmt.Println("Task A"); return nil }, func() error { fmt.Println("Task B"); return nil }, func() error { fmt.Println("Task C"); return nil }, func() error { fmt.Println("Task D"); return nil }, func() error { fmt.Println("Task E"); return nil }]
Output: (printed to console - order may vary)
Task A
Task B
Task C
Task D
Task E
Explanation: Five tasks are processed by three workers. Some workers will process multiple tasks.

Example 3: (Error Handling)

Input: numWorkers = 1, tasks = [func() error { fmt.Println("Task X"); return fmt.Errorf("simulated error") }, func() error { fmt.Println("Task Y"); return nil }]
Output: (printed to console)
Task X
Error: simulated error
Task Y
Explanation: The worker pool handles the error from Task X and continues processing Task Y.

Constraints

  • numWorkers must be a positive integer.
  • The task function must accept no arguments and return an error.
  • The task channel should be buffered. A buffer size of 10 is suggested.
  • The worker pool should be able to handle a large number of tasks (at least 100) without significant performance degradation.
  • The shutdown process should complete within a reasonable time (e.g., less than 1 second) after the stop signal is sent.

Notes

  • Consider using a sync.WaitGroup to ensure all workers have finished before the worker pool is considered fully shut down.
  • The "stop" signal can be represented by sending a nil value to the task channel.
  • Think about how to handle potential panics within the worker goroutines. For simplicity, you can just log the panic and continue.
  • Focus on clarity and correctness. Performance optimization is not the primary goal of this challenge, but efficient use of goroutines is expected.
  • The task channel should be closed after all workers have exited. This signals to any receivers that no more tasks will be sent.
Loading editor...
go