diff options
| author | Leitwolf11 | 2026-10-18 20:00:39 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-18 20:00:39 +0300 |
| commit | 8d1fb4807edc7920fbecd0554c9c61625827e19c (patch) | |
| tree | dd3d7e1d4f48ca27140a2bd5f970817ad02ae35a /Guide/CONCURRENCY.md | |
| download | progmasoft-cpp-8d1fb4807edc7920fbecd0554c9c61625827e19c.tar.gz | |
Write the first revision of Progmasoft C++ Style
Progmasoft C++ Style is a style guide for C++20, written for anyone
who wants a complete rule set to adopt. It writes down the conventions
the Progmasoft C++ projects already follow (naming, the clang-format
and clang-tidy profiles, exception-free error handling, Doxygen) and
decides what they left open.
Thirteen chapters under Guide/:
1. Principles: what the guide optimizes for and how to decide a case
it does not cover.
2. Formatting: the full .clang-format profile, the reason for each
choice, and the rules the tool cannot apply.
3. Naming: casing for every kind of name, acronyms, choosing words,
namespaces, files.
4. Files and headers: extensions, #pragma once, includes, what goes in
a header, the order inside a class, file size, vendored code.
5. Language: functions, auto, initialization, const and constexpr,
casts, integers, strings, enumerations, templates and concepts,
lambdas, control flow, and the features not used.
6. Types and classes: struct or class, final, construction, the rule of
zero, inheritance, operators, strong types, optional and variant.
7. Errors: no exceptions by default, Expected and Error, invariants,
fatal defects, destructors, diagnostics.
8. Ownership and memory: owners and borrowers, views and lifetimes,
RAII, containers and deterministic iteration, allocation, undefined
behavior.
9. Comments and documentation: Doxygen for public declarations,
comments that explain why, TODOs that name an issue.
10. C interfaces: C11 headers, prefixes, opaque types, ownership and
failure across the boundary, stability.
11. Concurrency: sharing less, threads, locks, thread-safety
annotations, atomics, statics.
12. Testing: unit tests, fuzzing, sanitizers, benchmarks, coverage.
13. Tooling: the compiler flags, the full .clang-tidy profile with the
reason for every disabled check, other checks, version pinning.
The guide prescribes no test, fuzzing or benchmark framework and no
error library; it prescribes how they are used. ADOPTING.md explains
how a project points to the guide, copies the profiles and records its
deviations. CONTRIBUTING.md explains how to propose a change.
The text is licensed under CC BY 4.0. The configuration and code
examples are dedicated to the public domain under CC0 1.0, so projects
can copy them under their own license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'Guide/CONCURRENCY.md')
| -rw-r--r-- | Guide/CONCURRENCY.md | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/Guide/CONCURRENCY.md b/Guide/CONCURRENCY.md new file mode 100644 index 0000000..9099de3 --- /dev/null +++ b/Guide/CONCURRENCY.md @@ -0,0 +1,73 @@ +<!-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> --> +<!-- SPDX-License-Identifier: CC-BY-4.0 --> + +# 11. Concurrency + +Concurrent code is correct only when it is correct for every interleaving, and testing finds few of them. The rules +below make the shared state small and visible, so that a reader can check it. + +## 11.1 Share less + +- **Prefer no sharing**: give each thread its own data and combine the results at the end. A result that several + threads compute is merged in a fixed order, so the output does not depend on scheduling. +- **Immutable data may be shared freely.** An object that is fully built before the threads start and never changed + afterwards needs no lock. +- **Everything else that more than one thread touches** is protected by one mutex, or is an atomic, and the comment of + the member says which. + +## 11.2 Threads + +- **`std::jthread`**, which joins on destruction and carries a stop token, instead of `std::thread`. +- **No detached threads.** Every thread has an owner that joins it. +- **A thread pool or an executor** for many short tasks, owned by the component that needs it, not a global. +- **Name long-lived threads** when the platform allows it, so they are recognizable in a debugger and a profiler. + +## 11.3 Locks + +- **`std::scoped_lock`** to lock, and `std::unique_lock` only when a condition variable or a deferred lock needs it. + Never call `lock()` and `unlock()` by hand. +- **Hold a lock for as short as possible**, and never while calling code you do not control: a callback, a virtual + function of an unknown implementation, I/O. +- **Lock several mutexes with one `std::scoped_lock`**, which avoids deadlock, or document a lock order and keep to it. +- **A condition variable waits with a predicate**: `condition.wait(lock, [&] { return ready; })`, never a bare `wait`, + which wakes spuriously. + +## 11.4 Thread-safety annotations + +With Clang, annotate the shared state and the functions that need a lock, and build with `-Wthread-safety` as an error: + +```cpp +class Cache final +{ +public: + void + Insert(Key key, Value value) EXAMPLE_EXCLUDES(mutex_); + +private: + Mutex mutex_; + std::unordered_map<Key, Value> entries_ EXAMPLE_GUARDED_BY(mutex_); +}; +``` + +The annotation macros wrap Clang's `capability`, `guarded_by`, `requires_capability` and `locks_excluded` attributes, +and expand to nothing on other compilers. `Mutex` is a thin wrapper of `std::mutex` that carries the `capability` +attribute, because the standard type does not. + +## 11.5 Atomics + +- **An atomic is for one value**, such as a counter or a flag. Two atomics that must change together need a mutex. +- **Sequentially consistent by default**: `counter.fetch_add(1)`. A weaker memory order (`acquire`, `release`, + `relaxed`) needs a comment that names the operation it synchronizes with and why the weaker order is enough. +- **No `volatile` for synchronization.** It is for memory-mapped hardware, not for threads. + +## 11.6 Statics and globals + +- A function-local `static` that is initialized once is thread-safe in C++; use it for a lazily built constant table. + Do not use it for mutable state. +- No mutable global state. When a process-wide resource is unavoidable, such as a signal handler or a C library that + must be initialized once, wrap it in one object with a documented lifetime. + +## 11.7 Testing concurrent code + +ThreadSanitizer runs the concurrent tests in CI (see [Testing](TESTING.md#123-sanitizers)). A test that relies on +`sleep` to order threads is wrong; it uses a latch, a barrier or a condition the threads wait for. |