Concurrency and Multithreading in C++: Advanced Level

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

Concurrency and Multithreading in C++: Advanced Level

\n\n

Modern applications demand maximum performance, and multithreading in C++ is one of the key tools for achieving it. However, basic mechanisms (std::thread, std::mutex) often lead to deadlocks, data races, and reduced efficiency. In this article, we will explore advanced techniques for working with concurrency in C++: from atomic operations to lock-free data structures and asynchronous tasks.

\n\n

1. Atomic Operations and Memory Ordering

\n\n

Classic mutexes are a heavy mechanism. For simple counters or flags, it is better to use std::atomic. It guarantees atomicity of read/write without locks. However, at an advanced level, it is important to understand memory ordering — the C++ memory model that defines the visibility of changes between threads.

\n\n

Main memory orders:

\n
    \n
  • memory_order_relaxed — minimal guarantees, only atomicity.
  • \n
  • memory_order_consume — rarely used, depends on data.
  • \n
  • memory_order_acquire — all subsequent reads/writes cannot be reordered before this operation.
  • \n
  • memory_order_release — all previous writes become visible to other threads after this operation.
  • \n
  • memory_order_acq_rel — combination of acquire and release.
  • \n
  • memory_order_seq_cst — the strictest, global sequential consistency (default).
  • \n
\n\n

Usage example:

\n
#include <atomic>\n#include <thread>\n#include <cassert>\n\nstd::atomic<bool> ready{false};\nint data = 0;\n\nvoid producer() {\n    data = 42;                                   // write data\n    ready.store(true, std::memory_order_release); // publish flag\n}\n\nvoid consumer() {\n    while (!ready.load(std::memory_order_acquire)) // wait for flag\n        ;\n    assert(data == 42); // guaranteed to see data == 42\n}\n\nint main() {\n    std::thread t1(producer);\n    std::thread t2(consumer);\n    t1.join();\n    t2.join();\n    return 0;\n}
\n\n

Here release and acquire guarantee that the write to data happens before setting the flag, and the read happens after. This is faster than a mutex.

\n\n

2. Lock-Free Data Structures

\n\n

Lock-free algorithms avoid locks by using atomic operations and CAS (Compare-And-Swap). A classic example is a lock-free stack based on a singly linked list.

\n\n

Implementation of a lock-free stack:

\n
#include <atomic>\n#include <memory>\n\ntemplate<typename T>\nclass LockFreeStack {\nprivate:\n    struct Node {\n        T data;\n        Node* next;\n        Node(const T& val) : data(val), next(nullptr) {}\n    };\n    std::atomic<Node*> head{nullptr};\n\npublic:\n    void push(const T& val) {\n        Node* new_node = new Node(val);\n        new_node->next = head.load(std::memory_order_relaxed);\n        while (!head.compare_exchange_weak(\n            new_node->next, new_node,\n            std::memory_order_release,\n            std::memory_order_relaxed)) {\n            // loop until CAS succeeds\n        }\n    }\n\n    bool pop(T& result) {\n        Node* old_head = head.load(std::memory_order_relaxed);\n        while (old_head != nullptr &&\n               !head.compare_exchange_weak(\n                   old_head, old_head->next,\n                   std::memory_order_acquire,\n                   std::memory_order_relaxed)) {\n            // wait\n        }\n        if (old_head == nullptr) return false;\n        result = old_head->data;\n        // Note: real code requires garbage collection or hazard pointers\n        delete old_head;\n        return true;\n    }\n};
\n\n

Important notes:

\n
    \n

Blogs

Book Recommendations