Thread Pools Multiple Queues

  • The queue can become a bottleneck

    • When a thread takes a task off the queue, it locks the queue
    • Other threads are blocked until this operation is complete
    • If there are many small tasks, this can affect performance
  • An alternative is to use a separate queue for each thread

    • A thread never has to wait to get to the next task
    • Uses more memory

Work Sharing

  • Can perform better than a single-queue pool
    • When there are many small tasks
  • If a thread's queue is empty, the thread is idle

Work Sharing Implementation

  • Replace the queue by a fixed size vector of queues

    • One element for each thread
  • "Round robin" scheduling

    • Put a new task on the next thread's queue
    • After the last element of the vector, go to the front element

Updated thr_pool.h and thr_pool.cpp


// reuse the concurrent_queue from previous lecture
#include "concurrent_queue.h"
#include <cstddef>
#include <functional>
#include <memory>
#include <thread>
#include <vector>

// Type alias to simplify the code
// All the task functions will have this type
using Func = std::function<void()>;

// Alias for concurrent_queue type
using Queue = concurrent_queue<Func>;

class thread_pool {
  // Each thread has its own queue of task functions
  std::unique_ptr<Queue[]> work_queues;
  std::vector<std::thread> threads; // Vector of thread objects

  void worker(size_t index); // Their entry point function

  unsigned int thread_count;
  size_t pos{0};

public:
  thread_pool();
  ~thread_pool();

  // Add a task to the queue
  void submit(Func func);
};


#include "thr_pool.h"
#include <iostream>
#include <memory>
#include <thread>

thread_pool::thread_pool() {
  thread_count = std::thread::hardware_concurrency() - 1;
  std::cout << "Creating a thread pool with " << thread_count << " threads\n";

  work_queues = std::make_unique<Queue[]>(thread_count);

  for (unsigned int i = 0; i < thread_count; ++i) {
    threads.push_back(std::thread{&thread_pool::worker, this, i});
  }
}

thread_pool::~thread_pool() {
  // Wait for the threads to finish
  for (auto &thr : threads)
    thr.join();
}

// Entry point function for the threads
void thread_pool::worker(size_t index) {
  while (true) {
    Func task;

    // Take a task function off the queue
    work_queues[index].pop(task);
    task();
  }
}

// Add a task to the queue
void thread_pool::submit(Func func) {
  work_queues[pos].push(func);

  pos = (pos + 1) % thread_count;
}