Launching and Managing Threads with C++11's Standard Thread Library

Concurrency and Threads

Concurrency describes the simultaneous execution of several independent activities within one system. On a single-core CPU, only one instruction stream can run at any instant, yet the operating system rapidly switches among tasks, giving the illusion of parallelism. Each switch incurs a context-switch cost: the current state, registers, and instruction pointer must be saved, the scheduler chooses the next runnable task, and the processor reloads the new context. These gray slices of overhead make concurrent execution slower than running tasks back-to-back on a single core.

True parallelism—hardware concurrency—arrives with multi-core CPUs, where each core can execute a separate insrtuction stream simultaneously.

Two common concurrency models exist:

  • Multi-process concurrency – Independent processes communicate through pipes, message queues, shared memory, semaphores, memory-mapped I/O, or sockets. IPC is heavyweight, slower, and resource-intensive.
  • Multi-thread concurrency – Threads are lightweight execution units that share the same virtual address space. Communication is often as simple as reading or writing global variables.

The C++11 Thread Facility

C++11 introduced <thread> and related headers, giving portable primitives for thread creation, synchronization, and atomic operations.

Spawning Threads

A std::thread object starts immediately upon construction. The caller must decide, before the object is destroyed, whether to join (block until completion) or detach (let the runtime reap resources).

Free and Static Functions

static void counterTask() {
    for (int i = 0; i <= 10; ++i) {
        std::cout << "counterTask id=" << std::this_thread::get_id()
                  << " value=" << i << '\n';
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    std::cout << "counterTask exiting\n";
}

void cpuReporter() {
    while (true) {
        std::cout << "cpuReporter id=" << std::this_thread::get_id()
                  << " cores=" << std::thread::hardware_concurrency() << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    std::thread t1(counterTask);   // runs counterTask
    t1.join();                     // wait for completion

    std::thread t2(cpuReporter);
    std::cout << "native handle: " << t2.native_handle() << '\n';
    t2.join();
}

Passing Arguments

Use std::ref to forward references safely:

void echoValue(int& v) {
    while (true) {
        std::cout << "echoValue sees " << v << " from thread "
                  << std::this_thread::get_id() << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    int value = 42;
    std::thread worker(echoValue, std::ref(value));
    worker.join();   // OK: value outlives thread
}

Detached threads must not access automatic variables that may vanish.

Sleeping Strategies

sleep_for pauses for a relative druation; sleep_until wakes at an absolute time point.

void steadyHeartbeat() {
    using clock = std::chrono::steady_clock;
    auto nextTick = clock::now();
    while (true) {
        nextTick += std::chrono::milliseconds(1000);
        std::this_thread::sleep_until(nextTick);
        std::cout << "tick from " << std::this_thread::get_id() << '\n';
    }
}

Functor Objects

Any object with an operator() can act as a thread target:

class Worker {
public:
    explicit Worker(std::string name) : name_(std::move(name)) {}
    void operator()() const {
        while (true) {
            std::cout << name_ << " running\n";
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
private:
    std::string name_;
};

int main() {
    Worker w("printer");
    std::thread job(w);
    job.join();
}

Member Functions

A pointer-to-member plus an object instance launches a member function in a new thread:

class Engine {
public:
    void spin() {
        while (true) {
            std::cout << "Engine spinning\n";
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
};

int main() {
    Engine e;
    std::thread t(&Engine::spin, &e);  // pass object pointer
    t.detach();                         // runs independently
}

Lambda Expressions

Lambdas capture surrounding variables and yield concise inline threads:

int main() {
    int shared = 123;
    std::thread lambdaThread([&] {
        while (true) {
            std::cout << "lambda sees " << shared
                      << " in thread " << std::this_thread::get_id() << '\n';
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    });
    lambdaThread.join();
}

Thread lifetime, ownership, and variable capture remain the programmer’s responsibility; misuse leads to data races or dangling references.

Tags: C++ multithreading std::thread Concurrency lambda

Posted on Sun, 27 Sep 2026 16:21:35 +0000 by zhabala