█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Programming Languages/C++/Concurrency in C++
60 minAdvanced

Concurrency in C++

After this lesson, you will be able to: Use std::thread, std::mutex, std::atomic, std::async, std::future for safe concurrent C++.

Concurrent C++ is hard. Get it right and you have a fast multi-core program. Get it wrong and you have data races, deadlocks, and bugs that only show up under load.

Prerequisites:Modern C++

std::thread basics

Spawn a thread; join before it goes out of scope.

tsx
#include <thread>
#include <iostream>
void work(int id) {
std::cout << "thread " << id << "\n";
}
int main() {
std::thread t1(work, 1);
std::thread t2(work, 2);
t1.join(); // wait for t1 to finish
t2.join(); // wait for t2 to finish
// forgetting to join = std::terminate at scope exit
}
// C++20: std::jthread auto-joins on destruction
// std::jthread t(work, 1); // no manual join needed

Mutex + lock_guard for shared state

Protect every shared variable with a mutex.

tsx
#include <mutex>
#include <thread>
#include <vector>
int counter = 0;
std::mutex m;
void increment() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(m); // RAII lock
++counter; // critical section
} // lock auto-releases here
}
int main() {
std::vector<std::thread> ts;
for (int i = 0; i < 10; ++i) ts.emplace_back(increment);
for (auto& t : ts) t.join();
std::cout << counter << "\n"; // 10000, correct
}
// Without the lock, counter is undefined, data race.
// C++20: std::scoped_lock for multiple mutexes (deadlock-free)

std::atomic for lock-free counters

Faster than mutex for simple types.

tsx
#include <atomic>
std::atomic<int> counter{0};
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
// shorthand: ++counter;
}
}

std::async + std::future for results

Run a function async; get its return value when ready.

tsx
#include <future>
#include <iostream>
int slowCompute(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * 2;
}
int main() {
std::future<int> f = std::async(std::launch::async, slowCompute, 21);
// do other work...
int result = f.get(); // blocks until ready
std::cout << result << "\n"; // 42
}

💡 Concurrency rules of thumb

Default: no shared state. Pass copies. If shared: prefer std::atomic for primitives, std::mutex for everything else. Use std::lock_guard / std::scoped_lock, never raw lock/unlock (exception unsafe). Hold locks for the shortest possible time. Use thread sanitizer (`-fsanitize=thread`) to catch races during testing.

Common mistakes

Forgetting to join (terminate at scope exit). Use jthread (C++20) or RAII wrappers. Forgetting `noexcept` on move operations of mutex-holding classes. Raw `m.lock()` + `m.unlock()` (exception = held lock = deadlock). Double-locking a non-recursive mutex from the same thread. Sharing a non-atomic int across threads (data race = UB).

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Modern C++ (C++17/20)
Back to C++
Build Systems: CMake→