Concurrency and Multithreading in C++: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Concurrency and Multithreading in C++



Modern applications demand maximum performance. Concurrency and multithreading in C++ allow efficient use of multi-core processors, speeding up task execution. Without understanding these concepts, it is impossible to create high-load systems, data processors, or game engines.



In this article, we will break down the key mechanisms of multithreading in C++, starting with basic std::thread and ending with advanced patterns. You will learn how to avoid data races, properly synchronize threads, and write safe parallel code.



Basics of Multithreading: std::thread and std::jthread



The std::thread class (C++11) is the foundation of multithreaded programming. It allows you to run a function or callable object in a separate thread. Let's look at a simple example:



#include <iostream>#include <thread>

void worker(int id) { std::cout << "Thread " << id << " started work"; std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Thread " << id << " finished";}

int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); return 0;}


Key points:

  • join() — the main thread waits for the child thread to finish
  • detach() — the thread continues running independently (risk of resource leak)
  • std::jthread (C++20) — automatically calls join() in the destructor


Always check joinable() before calling join() to avoid double joining.



Thread Synchronization: Mutexes and Locks



The main problem of multithreading is data races, when multiple threads simultaneously modify a shared variable. Mutexes are used for protection.



std::mutex and std::lock_guard



#include <iostream>#include <thread>#include <mutex>

std::mutex mtx;int counter = 0;

void increment() { for (int i = 0; i < 100000; ++i) { std::lock_guard<std::mutex> lock(mtx); ++counter; }}

int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Result: " << counter << ""; // 200000 return 0;}


std::lock_guard automatically releases the mutex when leaving the scope. For more flexible control, use std::unique_lock.



Deadlock and std::scoped_lock



A deadlock occurs when two threads are waiting for each other. The solution is to use std::scoped_lock (C++17), which acquires multiple mutexes atomically:



std::mutex m1, m2;

void safe_function() { std::scoped_lock lock(m1, m2); // safe work with two resources}


Parallel Algorithms and std::async



For executing asynchronous tasks without manual thread management, use std::async. It returns a std::future from which you can obtain the result.



#include <iostream>#include <future>#include <chrono>

int compute(int n) { std::this_thread::sleep_for(std::chrono::seconds(n)); return n * n;}

int main() { auto future1 = std::async(std::launch::async, compute, 2); auto future2 = std::async(std::launch::async, compute, 3); std::cout << "Results: " << future1.get() << ", " << future2.get() << ""; // 4, 9 return 0;}


Launch policies:

  • std::launch::async — launch in a new thread
  • std

Blogs

Book Recommendations