Main thread vs. Worker thread(s)

1. Main thread

– Gets created during startup
– It’s possibly the only thread
– Normally sets up the app/program
– Often ends up in an event queue


2. Worker thread(s)

– Get spawned manually or borrowed from a pool
– Make it possible to solve tasks in parallel
– Release some load from the main thread
– Possibly work on an event/task queue


Example (with 1 main and 1 worker thread)

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <queue>
 
class Logger
{
public:
    static void log(const std::string & msg)
    {
        static std::mutex logMutex;
        std::lock_guard<std::mutex> lock(logMutex);
        std::cout << msg << std::endl;
    }    
};
 
class PiCalculator
{
public:
    PiCalculator()
    : m_stopRequested(false), m_mutex(), m_queue(), m_condVar(),
      m_calcCount(0), m_worker(std::thread([this](){run();})) {}
 
    ~PiCalculator()
    {
        if (m_worker.joinable())
        {
            requestStop();
            m_worker.join();
        }
    }
 
    void addCalculation(unsigned long long & iter)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_queue.push(iter);
        m_condVar.notify_all();
    }
 
private:
    void requestStop()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_stopRequested = true;
        m_condVar.notify_all();
    }
 
    void run()
    {
        unsigned long long iter = 0;
        unsigned int calcNum = 0;
 
        // Worker thread - Gets work from the task queue
 
        while (!m_stopRequested)
        {
            {
                // unique_lock necessary for condVar.wait()
                std::unique_lock<std::mutex> lock(m_mutex);
 
                while (!m_stopRequested && m_queue.empty())
                {
                    // Releases mutex while waiting passively
                    m_condVar.wait(lock);
                }
 
                if (m_stopRequested) { return; }
 
                iter = m_queue.front();
                calcNum = m_calcCount++;
                m_queue.pop();
            }
 
            calculate(iter, calcNum);
        }
    }
 
    void calculate(unsigned long long iter, unsigned int calcNum)
    {
        // Worker thread - Executes a possibly long time task
 
        if (m_stopRequested) { return; }
 
        auto calcNumStr = std::to_string(calcNum);
        Logger::log("[" + calcNumStr + "]" + " Start calculation ...");
 
        double sum = 0.0;
        int sign = 1;
 
        for (unsigned long long i = 0; i < iter && !m_stopRequested; ++i)
        {
            sum += sign / (2.0 * i + 1.0);
            sign *= -1;
        }
 
        if (m_stopRequested) { return; }
 
        auto piStr = std::to_string(4.0 * sum);
        Logger::log("[" + calcNumStr + "]" + " ... finished: " + piStr);
    }
 
    std::atomic<bool> m_stopRequested;
    std::mutex m_mutex;
    std::queue<unsigned long long> m_queue;
    std::condition_variable m_condVar;
    std::atomic<unsigned int> m_calcCount;
    std::thread m_worker;
};
 
int main()
{
    // Main thread - Sets up the program
 
    Logger::log("Calculating Pi with the Taylor method");
    Logger::log("");
    Logger::log("Possible Input:");
    Logger::log("- <num> ... number of iterations");
    Logger::log("- quit  ... quits the process");
    Logger::log("");
 
    std::string input = "";
    PiCalculator piCalculator;
 
    // Main thread - Enters the main event loop
 
    while (true)
    {
        std::cin >> input;
 
        if (input == "quit") { break; }
 
        try
        {
            unsigned long long iter = std::stoull(input);
            piCalculator.addCalculation(iter);
        }
        catch (...)
        {
            Logger::log("The input was invalid");
        }
    }
 
    return 0;
}

With C++20 you can use std::jthread (“Cooperatively Interruptible Joining Thread”), which does the interruption request and the joining in its destructor.

jthread::~jthread()
{
    if (joinable())     // Not joined/detached yet
    {
        request_stop(); // Inform to stop asap
        join();         // Wait until finished
    }
}