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-startedstd::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