Thread#

Join provides a thread management class built on top of POSIX threads. The Thread class offers a simple interface for creating and managing threads with support for move semantics and non-blocking join operations.

Thread features:

  • callable objects — accepts functions, lambdas, and functors
  • move semantics — efficient thread ownership transfer
  • non-blocking join — check completion without blocking
  • thread cancellation — request thread termination
  • RAII-compliant — automatic cleanup on destruction

Creating threads#

Basic thread creation#

#include <join/thread.hpp>

using join;

void worker()
{
    // Thread work
}

Thread thread(worker);
thread.join();  // Wait for completion

With lambda#

Thread thread([]() {
    std::cout << "Thread running\n";
});

thread.join();

With arguments#

void task(int id, const std::string& name)
{
    std::cout << "Task " << id << ": " << name << "\n";
}

Thread thread(task, 42, "worker");
thread.join();

With member functions#

class Worker
{
public:
    void process(int value)
    {
        // Process value
    }
};

Worker worker;
Thread thread(&Worker::process, &worker, 100);
thread.join();

Thread operations#

Join#

Block until thread completes:

Thread thread([]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
});

thread.join();  // Blocks for ~2 seconds
// Thread completed

Try join (non-blocking)#

Check if thread completed without blocking:

Thread thread([]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
});

if (thread.tryJoin())
{
    // Thread already completed
}
else
{
    // Thread still running
    // Do other work...
    thread.join();  // Wait later
}

Cancel#

Request thread cancellation:

Thread thread([]() {
    while (true)
    {
        // Long-running work
    }
});

std::this_thread::sleep_for(std::chrono::seconds(1));
thread.cancel();  // Request cancellation and wait for termination

Note: Cancellation uses pthread_cancel, which may not work as expected if the thread doesn’t reach a cancellation point. Consider cooperative cancellation patterns instead.


Thread state#

Joinable#

Check if thread has an associated execution:

Thread thread1;  // Default constructed
bool j1 = thread1.joinable();  // false

Thread thread2([]() {});
bool j2 = thread2.joinable();  // true

thread2.join();
bool j3 = thread2.joinable();  // false (after join)

Running#

Check if thread is currently executing:

Thread thread([]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
});

bool r1 = thread.running();  // true

std::this_thread::sleep_for(std::chrono::seconds(3));
bool r2 = thread.running();  // false (completed)

thread.join();  // Safe, thread already done

Move semantics#

Threads support move operations for ownership transfer:

Thread createThread()
{
    return Thread([]() {
        // Work
    });
}

Thread thread1 = createThread();  // Move construction

Thread thread2;
thread2 = std::move(thread1);  // Move assignment

// thread1 is now empty (not joinable)
// thread2 owns the thread

Swap#

Thread thread1(task1);
Thread thread2(task2);

thread1.swap(thread2);  // Swap ownership

thread1.join();  // Joins what was thread2
thread2.join();  // Joins what was thread1

Usage patterns#

Simple task execution#

void processData(const std::vector<int>& data)
{
    Thread thread([&data]() {
        // Process in background
        for (int value : data)
        {
            // Heavy computation
        }
    });

    // Do other work

    thread.join();  // Wait for background processing
}

Thread pool#

class ThreadPool
{
public:
    ThreadPool(size_t numThreads)
    {
        for (size_t i = 0; i < numThreads; ++i)
        {
            _threads.emplace_back([this]() { worker(); });
        }
    }

    ~ThreadPool()
    {
        _stop = true;
        for (auto& thread : _threads)
        {
            thread.join();
        }
    }

    void submit(std::function<void()> task)
    {
        ScopedLock<Mutex> lock(_mutex);
        _tasks.push(std::move(task));
    }

private:
    void worker()
    {
        while (!_stop)
        {
            std::function<void()> task;

            {
                ScopedLock<Mutex> lock(_mutex);
                if (_tasks.empty())
                {
                    std::this_thread::sleep_for(std::chrono::milliseconds(10));
                    continue;
                }
                task = std::move(_tasks.front());
                _tasks.pop();
            }

            task();
        }
    }

    std::vector<Thread> _threads;
    std::queue<std::function<void()>> _tasks;
    Mutex _mutex;
    std::atomic_bool _stop{false};
};

Parallel computation#

void parallelSum(const std::vector<int>& data)
{
    size_t mid = data.size() / 2;
    int sum1 = 0, sum2 = 0;

    Thread thread([&]() {
        for (size_t i = 0; i < mid; ++i)
        {
            sum1 += data[i];
        }
    });

    for (size_t i = mid; i < data.size(); ++i)
    {
        sum2 += data[i];
    }

    thread.join();

    int total = sum1 + sum2;
    std::cout << "Total: " << total << "\n";
}

Background monitoring#

class Monitor
{
public:
    Monitor() : _running(true)
    {
        _thread = Thread([this]() {
            while (_running)
            {
                checkStatus();
                std::this_thread::sleep_for(std::chrono::seconds(1));
            }
        });
    }

    ~Monitor()
    {
        _running = false;
        _thread.join();
    }

private:
    void checkStatus()
    {
        // Monitor system status
    }

    Thread _thread;
    std::atomic_bool _running;
};

Cooperative cancellation#

Since pthread_cancel may not work reliably, use cooperative cancellation:

class Task
{
public:
    void start()
    {
        _stop = false;
        _thread = Thread([this]() { run(); });
    }

    void stop()
    {
        _stop = true;
        _thread.join();
    }

private:
    void run()
    {
        while (!_stop)
        {
            // Check _stop regularly
            // Do work

            if (_stop)
                break;
        }
    }

    Thread _thread;
    std::atomic_bool _stop{false};
};

Best practices#

  • Always join or cancel — threads must be joined or canceled before destruction
  • Use RAII wrappers — wrap threads in classes that manage their lifetime
  • Cooperative cancellation — prefer flag-based cancellation over pthread_cancel
  • Check running state — use tryJoin() for non-blocking completion checks
  • Move, don’t copy — threads are move-only, use std::move() for ownership transfer
  • Avoid exceptions in threads — unhandled exceptions terminate the program
  • Synchronize shared data — use mutexes to protect data accessed by multiple threads

Common pitfalls#

Forgetting to join#

// BAD: thread destroyed before joining
{
    Thread thread(task);
    // Destructor cancels the thread!
}

// GOOD: explicitly join
{
    Thread thread(task);
    thread.join();
}

Data races#

// BAD: race condition
int counter = 0;

Thread t1([&]() { ++counter; });
Thread t2([&]() { ++counter; });

t1.join();
t2.join();
// counter may not be 2!

// GOOD: use mutex
Mutex mutex;
int counter = 0;

Thread t1([&]() {
    ScopedLock<Mutex> lock(mutex);
    ++counter;
});

Thread t2([&]() {
    ScopedLock<Mutex> lock(mutex);
    ++counter;
});

t1.join();
t2.join();
// counter is definitely 2

Dangling references#

// BAD: local variable destroyed before thread finishes
void bad()
{
    std::vector<int> data = {1, 2, 3};

    Thread thread([&data]() {
        // data may be destroyed already!
        for (int x : data) { }
    });

    // Function returns, data destroyed, thread still running
}

// GOOD: join before return or copy data
void good()
{
    std::vector<int> data = {1, 2, 3};

    Thread thread([data]() {  // Copy data
        for (int x : data) { }
    });

    thread.join();  // Or ensure thread completes
}

Comparison with std::thread#

Featurejoin::Threadstd::thread
Move semantics✅ Yes✅ Yes
Cancellation✅ Built-in❌ Manual
Non-blocking jointryJoin()❌ No
Running checkrunning()❌ No
Auto-cancel on destroy✅ Yes⚠️ Terminates program

Summary#

FeatureSupported
Thread creation
Lambda support
Member function support
Move semantics
Non-blocking join
Running state check
Thread cancellation
RAII cleanup