C+
Concurrency

Threads and atomics

This example combines two checked recipes from the C+ source tree: docs/examples/recipes/parallel_sum and docs/examples/recipes/concurrent_counter.

Together they back the concurrency claim without overclaiming. The first recipe shows the preferred safe shape: partition data, move each piece into a worker, then join. The second shows the explicit unsafe atomic shape for the rarer case where workers must share mutable state.

Partition and join

parallel_sum splits 1..1000 into two owned ranges. Each range is moved into one worker with thread::spawn_with, and the parent joins both handles:

import "stdlib/thread" as thread;

struct Range { start: i64, end: i64 }

fn sum_range(r: Range) -> i64 {
    let mut total: i64 = 0 as i64;
    let mut i: i64 = r.start;
    while i < r.end {
        total = total +% i;
        i = i +% (1 as i64);
    }
    return total;
}

let h1: thread::JoinHandle[i64] = thread::spawn_with::[Range, i64](left, sum_range);
let h2: thread::JoinHandle[i64] = thread::spawn_with::[Range, i64](right, sum_range);

let total: i64 = h1.join() +% h2.join();
if total != (500500 as i64) { return 1; }

There is no shared mutable state in this recipe, so there is no lock or atomic operation for the user to reason about.

Shared counter with atomics

concurrent_counter is the companion recipe for the case that cannot be partitioned. Two workers increment the same heap-allocated u64 100,000 times each, using an explicit atomic fetch-add:

import "stdlib/thread" as thread;
import "stdlib/atomic" as atomic;

fn bump(counter: *u64) -> i32 {
    let mut i: i32 = 0 as i32;
    while i < (100000 as i32) {
        let _prev: u64 = unsafe {
            atomic::atomic_fetch_add_u64(counter, 1 as u64, atomic::Ordering::SeqCst)
        };
        i = i +% (1 as i32);
    }
    return 0 as i32;
}

After joining both workers, the parent loads the final value and expects exactly 200000:

let final_val: u64 = unsafe {
    atomic::atomic_load_u64(counter, atomic::Ordering::SeqCst)
};

if final_val != (200000 as u64) { return 1; }
return 0;

Reproduce

From docs/examples/recipes/parallel_sum:

cpc build
./target/debug/parallel_sum

From docs/examples/recipes/concurrent_counter:

cpc build
./target/debug/concurrent_counter

Expected result: both programs exit with code 0.

The credibility point is the contrast: ordinary parallel work stays in the safe partition-and-join shape, while shared mutable state is made explicit with raw pointers, unsafe, and atomic ordering.


‹ Back to all examples