Atomics and Async

Back

Loading concept...

🚀 C++ Concurrency: Atomics and Async

The Kitchen of a Busy Restaurant 🍳

Imagine a super busy restaurant kitchen. Multiple chefs work at the same time. They all need to share ingredients, update the order board, and coordinate who does what. If they don’t coordinate well, chaos happens—two chefs grab the same tomato, or orders get mixed up!

C++ concurrency is like being the head chef who organizes everything so meals come out perfectly, even with many cooks working together.


🧩 What We’ll Learn

graph TD A["Concurrency in C++"] --> B["Atomic Operations"] A --> C["Futures & Promises"] A --> D["async Function"] A --> E["std::jthread"] B --> B1["Safe counting"] C --> C1["Send results later"] D --> D1["Easy background tasks"] E --> E1["Smart threads"]

1️⃣ Atomic Operations

The Shared Whiteboard Problem

Picture a whiteboard where chefs write how many pizzas are ready. If two chefs try to update it at the exact same moment, the number gets messed up!

Atomic operations are like having a magic marker that only ONE person can hold at a time. When you use it, your update is complete and correct—no mixing up.

What “Atomic” Means

The word atomic means “indivisible”—like an atom (the smallest piece). An atomic operation happens all at once or not at all. No half-done work!

Simple Example

Without atomic (BAD):

int counter = 0;

// Thread 1
counter++;  // Read, add, write

// Thread 2
counter++;  // Read, add, write

// Result might be 1 instead of 2!

With atomic (GOOD):

#include <atomic>

std::atomic<int> counter{0};

// Thread 1
counter++;  // One complete action

// Thread 2
counter++;  // One complete action

// Result is always 2!

Key Atomic Operations

Operation What It Does Example
load() Read the value safely int x = counter.load();
store() Write a new value safely counter.store(5);
++ / -- Add or subtract one counter++;
fetch_add() Add and return old value counter.fetch_add(3);
exchange() Swap values counter.exchange(10);

When to Use Atomics

✅ Simple counters shared between threads ✅ Flags (true/false signals) ✅ Small data that many threads read/write

❌ NOT for complex objects ❌ NOT for data that needs multiple steps


2️⃣ Futures and Promises

The Restaurant Order Ticket 🎫

When you order food at a restaurant, you get a ticket. The ticket is a promise that your food will come. You can go sit down and wait. When the food is ready, you use your ticket to get it.

In C++:

  • Promise = The kitchen’s commitment to make your food
  • Future = Your ticket to pick up the result

How They Work Together

graph LR A["Promise"] -->|"set_value#40;#41;"| B["Shared State"] B -->|"get#40;#41;"| C["Future"] style A fill:#ff6b6b style B fill:#ffd93d style C fill:#6bcb77

Simple Example

#include <future>
#include <iostream>

int main() {
    // Create a promise (kitchen)
    std::promise<int> myPromise;

    // Get the future (your ticket)
    std::future<int> myFuture =
        myPromise.get_future();

    // Kitchen prepares the result
    myPromise.set_value(42);

    // You collect your order
    int result = myFuture.get();

    std::cout << result;  // Prints: 42
}

Using with Threads

#include <future>
#include <thread>

void cookPizza(std::promise<std::string> p) {
    // Chef works hard...
    p.set_value("Pepperoni Pizza Ready!");
}

int main() {
    std::promise<std::string> orderPromise;
    std::future<std::string> orderTicket =
        orderPromise.get_future();

    // Start the chef working
    std::thread chef(cookPizza,
        std::move(orderPromise));

    // Do other things while waiting...

    // Pick up your order (waits if not ready)
    std::string food = orderTicket.get();

    chef.join();
}

Important Rules

Rule Why
get() only once The ticket is used up
get() blocks Waits until result is ready
Move the promise Can’t copy, must move

3️⃣ The async Function

The Easiest Way to Cook in Background! 👨‍🍳

Remember all that promise/future setup? What if someone did it all for you? That’s std::async!

It’s like telling an assistant: “Go make this happen, give me a ticket to get the result later.”

Super Simple Example

#include <future>
#include <iostream>

int bakeACake() {
    // Baking takes time...
    return 42;  // Returns delicious cake
}

int main() {
    // Start baking in background
    std::future<int> cake =
        std::async(bakeACake);

    // Do other things while baking...
    std::cout << "Preparing plates...\n";

    // Get the finished cake
    int result = cake.get();
    std::cout << "Cake: " << result;
}

Launch Policies

You can tell async HOW to run the task:

Policy What It Means
std::launch::async MUST run in new thread
std::launch::deferred Run when you call get()
Default System decides
// Force new thread
auto f1 = std::async(
    std::launch::async,
    bakeACake
);

// Run later when needed
auto f2 = std::async(
    std::launch::deferred,
    bakeACake
);

Passing Arguments

int add(int a, int b) {
    return a + b;
}

int main() {
    auto result = std::async(add, 10, 20);
    std::cout << result.get();  // Prints: 30
}

Why async is Awesome

✅ No manual thread management ✅ No manual promise/future setup ✅ Clean, simple code ✅ Exception handling built-in


4️⃣ std::jthread (C++20)

The Self-Cleaning Chef 🧹

Regular threads (std::thread) are like chefs who leave dirty dishes. YOU must clean up (call join() or detach()).

std::jthread is like a chef who automatically cleans up when they leave! Plus, you can politely ask them to stop cooking.

The “j” Means “Joining”

#include <thread>

void regularThread() {
    std::thread t([]{ /* work */ });
    // MUST call t.join() or t.detach()
    // Or program crashes!
    t.join();  // Don't forget!
}

void smartThread() {
    std::jthread jt([]{ /* work */ });
    // Automatically joins when jt
    // goes out of scope!
    // No cleanup needed!
}

Stop Tokens: Polite Cancellation

The coolest feature! You can ask a jthread to stop nicely.

#include <thread>
#include <iostream>

void worker(std::stop_token token) {
    while (!token.stop_requested()) {
        std::cout << "Working...\n";
        // Do some work
    }
    std::cout << "Stopping politely!\n";
}

int main() {
    std::jthread chef(worker);

    // Let chef work for a bit...
    std::this_thread::sleep_for(
        std::chrono::seconds(2)
    );

    // Ask chef to stop
    chef.request_stop();

    // jthread auto-joins here!
}

Comparing thread vs jthread

Feature std::thread std::jthread
Auto-join ❌ No ✅ Yes
Stop token ❌ No ✅ Yes
Forget to join 💥 Crash ✅ Safe
C++ version C++11 C++20

Stop Callback

Want something to happen when stop is requested?

void worker(std::stop_token token) {
    std::stop_callback cb(token, []{
        std::cout << "Cleanup time!\n";
    });

    while (!token.stop_requested()) {
        // Work...
    }
}

🎯 Quick Summary

graph TD A["Need shared&lt;br&gt;counter?"] -->|Yes| B["Use Atomic"] A -->|No| C{Need result<br>from thread?} C -->|Yes| D{Want easy<br>setup?} D -->|Yes| E["Use async"] D -->|No| F["Use Promise&lt;br&gt;+ Future"] C -->|No| G{Need stop<br>support?} G -->|Yes| H["Use jthread"] G -->|No| I["Use thread"]

🏆 Key Takeaways

  1. Atomic = Magic lock-free updates for simple values
  2. Promise + Future = Send results between threads
  3. async = Easy background tasks with automatic setup
  4. jthread = Smart threads that clean up and can be cancelled

🎪 Final Analogy Recap

Concept Restaurant Analogy
Atomic Magic marker only one can hold
Promise Kitchen’s commitment
Future Your order ticket
async “Just handle it for me”
jthread Self-cleaning chef

You’re now ready to coordinate your C++ kitchen like a pro head chef! 🧑‍🍳✨

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.