examples/sensor-init/ in the source tree. Policy: lock_free, via make_memoized.
This is the std::call_once use case — run an initialiser exactly once across threads — except the return value (a device handle) is kept and handed to every caller.
The program
#include <memoized_invoke.hh>
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
using fedem::utility::lock_free;
using fedem::utility::make_memoized;
namespace
{
std::atomic< int > init_calls{ 0 };
struct device_handle
{
int address = 0;
bool ready = false;
};
device_handle init_device( int address )
{
init_calls.fetch_add( 1, std::memory_order_relaxed );
std::cout << "[init] configuring device 0x" << std::hex << address
<< std::dec << '\n';
// A real bus transaction takes time; make the race genuine.
std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) );
return device_handle{ address, true };
}
} // namespace
int main( )
{
constexpr int N = 16;
auto device = make_memoized< lock_free >( &init_device, 0x3f );
std::vector< std::thread > threads;
std::vector< device_handle > seen( N );
for( int i = 0; i < N; ++i )
threads.emplace_back( [ &device, &seen, i ]( ) { seen[ i ] = device( ); } );
for( auto& t : threads )
t.join( );
device_handle const& h = device.value( );
std::cout << "device ready: id=0x" << std::hex << h.address << std::dec
<< " status=" << ( h.ready ? "OK" : "FAIL" ) << '\n';
std::cout << "init ran " << init_calls.load( ) << " time(s) across " << N
<< " threads\n";
bool all_same = true;
for( auto const& s : seen )
if( s.address != h.address || s.ready != h.ready )
all_same = false;
std::cout << ( all_same ? "all threads observed the same handle"
: "MISMATCH: threads saw different handles" )
<< '\n';
return ( init_calls.load( ) == 1 && all_same ) ? 0 : 1;
}
Reading it
make_memoized<lock_free>( &init_device, 0x3f ) — CTAD would give single_threaded; the factory is how you name lock_free. device is a memoized_invoke<lock_free, device_handle(*)(int), int>.
The 16-thread fan-out. Every thread calls device(). Inside memoized_invoke, each does a try_enter() — one compare_exchange_strong from not-started to running. Exactly one wins and runs init_device; the other fifteen call wait() and park on std::atomic::wait until the winner's mark_done() stores done and notify_all()s.
std::this_thread::sleep_for(5ms) inside the initialiser is there to make the race real — without it the winner would often finish before the others even reach try_enter(), and you would not be testing anything.
device.value() — a const reference to the cached handle, no copy. Safe here because every thread has joined, so is_done() is certainly true.
init_calls is std::atomic<int> because it is incremented from the worker thread and read from main. It ends at 1 — that is the assertion the program's exit code encodes.
Output
Deterministic, because the only thread-produced line is the single [init] from the winner (printed before any join completes), and everything else is printed by main after all joins:
[init] configuring device 0x3f
device ready: id=0x3f status=OK
init ran 1 time(s) across 16 threads
all threads observed the same handle
A caveat this example does not show
If you copy device (or move it), the lock_free policy's state resets to not-started — std::atomic cannot be copied. The copy would re-run init_device on its first call even though it carries the copied handle. See Reference › lock_free. Keep the one object and share references to it.
See also
- Reference › lock_free — the CAS +
atomic::waitmechanism. - Reference › make_memoized.
- User Guide › Exception Safety & Threads — what happens if the initialiser throws.

