Skip to content

03. Threads

Duncan Crutchley edited this page Apr 22, 2019 · 19 revisions

Overview

Implementing threads in C++ prior to C++11 was fairly tedious and platform specific. You could make these much simpler using Boost threads but since C++11 we have built-in threading.

It is still however a good idea to wrap your threads in classes to nicely manage them and to save having to repeat yourself when writing threaded code.

As such this part of CoreLibrary has several classes related to threading that aim to make life a bit easier when implementing threading.

I’ll show some examples of the threading utility classes providing threaded base class, message queue thread, event triggered thread, as well as thread group management, thread-safe queue and a bounded buffer.

Examples

These examples are in no way exhaustive in demonstrating all you can do with the code in the Threads sub-library within CoreLibrary but they should give an idea of general usage.

Look in the unit tests for more examples.

Thread Group

Example 1

Creating a group of threads from functions.

#include <iostream>      // For std::cout.
#include <mutex>         // For std::mutex and std::lock_guard.
#include <chrono>        // To support std::this_thread::sleep_for.
#include "ThreadGroup.h" // For core_lib::threads::ThreadGroup.

// Make sure we can use ThreadGroup.
using core_lib::threads::ThreadGroup;

// A mutex to protect access to std::cout.
std::mutex g_coutMutex;

void ThreadFunc()
{
    // Grab our thread ID.
    auto id = std::this_thread::get_id();

    // Do some work for 100 iterations, after which this thread
    // will terminate.
    for (int i = 1; i <= 100; ++i)
    {
        {
            // Protect access to std::cout as we'll be accessing 
            // it from multiple threads.
            std::lock_guard<std::mutex> lock(g_coutMutex);
            std::cout << "Thread ID: " << id << ", count: " << i << std:endl;
        }

        // Wait for a short period of time.
        std::this_thread::sleep_for(10ms);
    }
}

unsigned int GetNumThreads()
{
    // Create n threads each running the same function.
    auto n = std::thread::hardware_concurrency()
    
    if (0 == n)
    {
        n = 8;
    }

    return n;
}

int main()
{
    // Create thread group.
    ThreadGroup tg;

    // Create n threads each running the same function.
    auto n = GetNumThreads();
    
    for (unsigned int i = 0; i < n; ++i)
    {
        auto id = tg.CreateThread(ThreadFunc)->get_id();

        // Protect access to std::cout as we'll be accessing 
        // it from multiple threads.
        std::lock_guard<std::mutex> lock(g_coutMutex);
        std::cout << "Thread created with ID: " << id << std:endl;
    }

    // Wait for the 8 threads to finish.
    tg.JoinAll();

    return 0;
}

Example 2

Adding std::thread objects to a thread group.

// This example is the same as Example 1 except for the main function.

#include <memory> // For std::make_unique.

int main()
{
    // Create thread group.
    ThreadGroup tg;

    // Create n threads each running the same function.
    auto n = GetNumThreads();
    
    for (unsigned int i = 0; i < n; ++i)
    {
        auto t = std::make_unique<std::thread>(ThreadFunc);
        
        {
            // Protect access to std::cout as we'll be accessing 
            // it from multiple threads.
            std::lock_guard<std::mutex> lock(g_mutex);
            std::cout << "Thread created with ID: " << t->get_id() << std:endl;
        }

        tg.AddThread(t.release());
    }

    // Wait for the 8 threads to finish.
    tg.JoinAll();

    return 0;
}

Sync Event

Example 1

#include <iostream>     // For std::cout.
#include <mutex>        // For std::mutex and std::lock_guard.
#include <thread>       // For std::thread.
#include <chrono>       // To support std::this_thread::sleep_for.
#include "SyncEvent.h"  // For core_lib::threads::SyncEvent.

// Make sure we can use SyncEvent.
using core_lib::threads::SyncEvent;

// A mutex to protect access to std::cout.
std::mutex g_coutMutex;

void ThreadFunc(SyncEvent& event)
{
    // Grab our thread ID.
    auto id = std::this_thread::get_id();

    // Do some work for 100 iterations, after which this thread
    // will terminate.
    for (int i = 1; i <= 100; ++i)
    {
        {
            // Protect access to std::cout as we'll be accessing 
            // it from multiple threads.
            std::lock_guard<std::mutex> lock(g_coutMutex);
            std::cout << "Thread ID: " << id << ", count: " << i << std:endl;
        }

        // Wait for a short period of time.
        std::this_thread::sleep_for(10ms);
    }

    // Signal our event before the thread terminates we stop waiting.
    event.Signal();
}

int main()
{
    // Create sync event.
    SyncEvent event;   
    
    // Create a thread.
    auto t = std::thread(ThreadFunc, event); 

    {
        // Protect access to std::cout as we'll be accessing 
        // it from multiple threads.
        std::lock_guard<std::mutex> lock(g_coutMutex);
        std::cout << "Thread created with ID: " << t->get_id() << std:endl;
    }

    // Wait indefinitely for the thread to signal the event.
    event.Wait();

    // Make sure thread has really finished.
    if (t.joinable())
    {
        t.join();
    }
    
    return 0;
}

More coming soon...

Clone this wiki locally