C++ CONCURRENCY SUPPORT LIBRARY
See my other projects: turpin.dev
Forward
This is an exploration of the headers referenced in the cppreference.com concurrency support library documentation. The source is compiled, tested and benchmarked on each commit. Additionally, the C-style comments are converted to markdown prior to rendering this web page; this is purely to aid legibility: all documentation really is in the code itself. Just for kicks -- and to keep repeat visits fresh -- the chapters are shuffled nightly.
Each source file can be run standalone in Godbolt.
g++-12 file.cxx -std=c++23 -O1 -lgtest -lgtest_main
Further reading
- C++ High Performance: Master the art of optimizing the functioning of your C++ code, 2nd Edition -- Bjorn Andrist, Viktor Sehr, Ben Garney
- C++ Concurrency in Action, Second Edition -- Anthony Williams
#include <new>
#include "gtest/gtest.h"
#include <new>
You can query cache line size programmatically with these constants. Ensure data used together by a single thread are co-located; and conversely, avoid false sharing by keeping unrelated data apart.
std::hardware_destructive_interference_size
TEST(new, interference) {
struct {
alignas(std::hardware_destructive_interference_size) int x0;
alignas(std::hardware_destructive_interference_size) int x1;
alignas(std::hardware_destructive_interference_size) int x2;
} together;
struct {
alignas(std::hardware_constructive_interference_size) int x0;
alignas(std::hardware_constructive_interference_size) int x1;
alignas(std::hardware_constructive_interference_size) int x2;
} apart;
EXPECT_GE(&together.x1 - &together.x0, 16);
EXPECT_LT(&apart.x1 - &apart.x0, std::hardware_destructive_interference_size);
EXPECT_EQ(std::hardware_destructive_interference_size, 64);
EXPECT_EQ(std::hardware_constructive_interference_size, 64);
}
#include <atomic>
#include "gtest/gtest.h"
#include <atomic>
Update a variable thread safely. Can be used with any built-in type, or in fact, anything that is "trivially constructible".
std::atomic
TEST(atomic, atomic) {
auto i = std::atomic{0uz};
// This is thread safe
++i;
EXPECT_EQ(i, 1);
}
std::atomic_ref
Wrap a non-atomic thing in atomic love.
TEST(atomic, atomic_ref) {
auto i = 0uz;
auto ii = std::atomic_ref{i};
// This is also thread safe
++ii;
EXPECT_EQ(ii, i);
EXPECT_EQ(ii, 1);
}
#include <thread>
#include "gtest/gtest.h"
#include <algorithm>
#include <ranges>
#include <thread>
#include <vector>
The go-to platform-independent thread API. It's been a lot easier since
std::thread
was introduced in C++11.
std::thread
You must call join()
on the thread after creating it, otherwise bad
things. Typically this is managed using a vector of threads.
TEST(thread, thread) {
auto threads = std::vector<std::thread>{};
// Create threads
for ([[maybe_unused]] const auto _ : std::ranges::iota_view{1, 10})
threads.emplace_back([] {});
// Catch threads
for (auto &t : threads)
if (t.joinable())
t.join();
}
std::jthread
A joining thread is the same as a regular thread but has an implicit join()
in
the destructor. You can still join a std::jthread
, of course, which can be a
convenient synchronisation point.
TEST(thread, jthread) {
{
std::jthread t{[] {}};
}
// join() is called when it goes out of scope
}
std::this_thread
Useful functions within a thread.
TEST(thread, functions) {
// Suggest reschedule of threads
std::this_thread::yield();
// Get the ID of the current thread, useful for tracking/logging
const auto id = std::this_thread::get_id();
EXPECT_NE(id, std::thread::id{});
}
#include <barrier>
#include "gtest/gtest.h"
#include <barrier>
#include <thread>
A barrier is a multi-use latch. It is released when enough threads are queued up. Unlike a regular latch it also gives you the option of calling a routine when the latch is released.
std::barrier
TEST(latch, barrier) {
// Function to call when barrier opens
auto finished = bool{false};
const auto we_are_done = [&]() { finished = true; };
// Initialise barrier with the number of threads
std::barrier b(2, we_are_done);
// Thread function
const auto func = [&]() { b.arrive_and_wait(); };
// Create our threads (note braces for scope)
{
std::jthread t1{func};
std::jthread t2{func};
}
EXPECT_TRUE(finished);
}
#include <future>
#include "gtest/gtest.h"
#include <future>
std::async
is a powerful way to handle return values from multiple threads,
think of it like pushing a calculation into the background. It executes a
function asynchronously and returns a std::future
that will eventually hold
the result of that function call. Quite a nice way to reference the result of
calculation executed in another thread. Of course you must factor in the
overhead of actually creating the thread -- 20µs, say; but your mileage may
vary.
It's worth mentioning that a std::future
is only moveable, but the Standard Library provides a shared future; so you can start multiple threads and pass in a value you don't yet know.
std::async
TEST(thread, async) {
// Calculate some things in the background
auto a = std::async(std::launch::async, [] { return 1; });
auto b = std::async(std::launch::async, [] { return 2; });
auto c = std::async(std::launch::async, [] { return 3; });
// Block until they're all satisfied
const auto sum = a.get() + b.get() + c.get();
EXPECT_EQ(sum, 6);
}
#include <latch>
#include "gtest/gtest.h"
#include <latch>
#include <thread>
A latch is a single-use synchronisation primitive; single-use meaning you have to reset it. Really you could just always use a barrier which doesn't need resetting.
std::latch
TEST(latch, latch) {
// Initialise latch with the number of threads
auto l = std::latch{2};
// Variable to test how many threads have been allowed through the latch
auto i = std::atomic{0uz};
// Thread function waits for the others then updates a variable atomically
const auto func = [&]() {
l.arrive_and_wait();
++i;
};
// Create our threads
auto t1 = std::thread{func};
auto t2 = std::thread{func};
// Remember to join
t1.join();
t2.join();
EXPECT_EQ(i, 2);
}
#include <execution>
#include "gtest/gtest.h"
#include <execution>
#include <numeric>
Many of the Standard Library algorithms can take an execution policy, which is quite an exciting way to parallelise existing code. But remember it offers no thread safety: you must still protect your data as you would for any other threads. You also need to link against the TBB library.
// Create a big (-ish) chunk of data to play with
static const std::vector<int> vec = [] {
std::vector<int> v(10000);
std::iota(begin(v), end(v), 0);
return v;
}();
Some algorithms also have an _if
version that takes predicate: e.g.,
std::replace
and std::replace_if
.
std::sort
std::copy
std::transform
std::accumulate
std::for_each
std::reduce
std::inclusive_scan
std::exclusive_scan
std::transform_reduce
std::remove
std::count
std::max_element
std::min_element
std::find
std::generate
std::execution::par
Let's test each policy, and confirm the sums are equal.
It must be noted, though, that you must not throw exceptions in these routines
(even for the sequential option) or else crash and burn (std::terminate
.)
TEST(thread, execution_policy) {
// Sequential -- bof
const auto s0 = std::reduce(std::execution::seq, begin(vec), cend(vec));
// Parallel -- simple threads only
const auto s1 = std::reduce(std::execution::par, cbegin(vec), cend(vec));
// Parallel -- throw the whole tool shed at it
const auto s2 =
std::reduce(std::execution::par_unseq, cbegin(vec), cend(vec));
// Parallel -- vectorisation only
const auto s3 = std::reduce(std::execution::unseq, cbegin(vec), cend(vec));
EXPECT_EQ(s0, s1);
EXPECT_EQ(s0, s2);
EXPECT_EQ(s0, s3);
}
#include <condition_variable>
#include "gtest/gtest.h"
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
std::condition_variable
Like a mutex but with an additional predicate.
TEST(condition_variable, notify_one_with_predicate) {
std::mutex m;
std::condition_variable cv;
std::queue<int> q;
// Consumer thread waits for data to change
std::jthread consumer{[&]() {
std::unique_lock<std::mutex> lock(m);
// Wait until the data change
cv.wait(lock, [&] { return not q.empty(); });
// Empty the queue
while (not q.empty())
q.pop();
}};
// Producer thread updates data
std::jthread producer{[&]() {
std::unique_lock<std::mutex> lock(m);
q.push(0);
q.push(1);
q.push(2);
EXPECT_FALSE(q.empty());
// Notify the other thread that something has changed
cv.notify_one();
}};
// You don't have to join a thread but it offers a convenient synchonrisation
// point
consumer.join();
EXPECT_TRUE(q.empty());
}
#include <semaphore>
#include "gtest/gtest.h"
#include <atomic>
#include <chrono>
#include <semaphore>
#include <thread>
std::counting_semaphore
Like std::mutex
but you're saying you have multiple instances of a resource
available for concurrent access.
TEST(semaphore, counting_semaphore) {
// Initialise to 2 resources and make them both available
auto sem = std::counting_semaphore<2>{2};
auto i = std::atomic{0uz};
// Grab one before starting the threads
sem.acquire();
// Create threads, the first will pass straight through
std::jthread t1{[&] {
sem.acquire();
++i;
}};
// This thread will block
std::jthread t2{[&] {
sem.acquire();
++i;
}};
// Check only one thread has updated the value
std::this_thread::sleep_for(std::chrono::microseconds{1});
EXPECT_EQ(i, 1);
// Release the second thread
sem.release();
// Confirm it has changed afterwards
std::this_thread::sleep_for(std::chrono::microseconds{1});
EXPECT_EQ(i, 2);
}
std::binary_semaphore
A binary semaphore is just a specialisation of a counting semaphore. These are equivalent:
auto sem = std::binary_semaphore{1};
auto sem = std::counting_semaphore<1>{1};
Let's create one and make it available immediately.
TEST(semaphore, binary_semaphore) {
// Initialise semaphore to available
auto sem = std::binary_semaphore{1};
auto i = size_t{0};
// Grab the semaphore before starting the thread
sem.acquire();
// Create thread and try semaphore
std::jthread t{[&] {
sem.acquire();
++i;
}};
// Check the thread is blocked
std::this_thread::sleep_for(std::chrono::microseconds{1});
EXPECT_EQ(i, 0);
// Release the thread
sem.release();
// Confirm value has changed afterwards
std::this_thread::sleep_for(std::chrono::microseconds{1});
EXPECT_EQ(i, 1);
}
#include <stop_token>
#include "gtest/gtest.h"
#include <semaphore>
#include <stop_token>
#include <thread>
std::stop_token
Built-in method to make a jthread
stop.
Instruct the thread to exit its processing loop. I've used a semaphore to make the calling thread wait for the processing thread to tidy up.
TEST(thread, stop_token_explicit) {
using namespace std::literals::chrono_literals;
// Update this variable when we leave the stop token loop
auto i = 0uz;
// Create a semaphore to interact with the processing thread
std::binary_semaphore sem{0};
// Create processing thread
std::jthread t{[&](std::stop_token stop_token) {
// Do some work until told otherwise
while (not stop_token.stop_requested())
std::this_thread::sleep_for(1us);
// Our work here is done, update variable
++i;
// Tell the calling thread to continue
sem.release();
}};
// Check variable hasn't been updated, the processing thread is in its main
// loop at this point
EXPECT_EQ(i, 0);
// Tell the processing thread to stop
t.request_stop();
// Wait for it to finish
sem.acquire();
// Check variable has been updated
EXPECT_EQ(i, 1);
}
std::jthread
also stops implicitly when the thread goes out of scope.
TEST(thread, stop_token_implicit) {
// Update this variable when we leave the stop token loop
auto i = 0uz;
// Create a semaphore to interact with the processing thread
std::binary_semaphore sem{0};
// Create thread, it stops when it goes out of scope
{
std::jthread t{[&](std::stop_token stop_token) {
// Do some work until told otherwise
while (not stop_token.stop_requested())
std::this_thread::sleep_for(std::chrono::microseconds{1});
// Our work here is done, update variable and poke the calling thread
++i;
sem.release();
}};
// Check variable hasn't been updated, processing thread is doing its thing
EXPECT_EQ(i, 0);
// Thread goes out of scope and calls stop implicitly
}
// Check variable has been updated when processing thread signals
sem.acquire();
EXPECT_EQ(i, 1);
}
#include <mutex>
#include "gtest/gtest.h"
#include <mutex>
A mutual exclusion lock is the starting point for all things concurrent, offering a standard way to protect access a resource; but there are multiple ways to unlock it.
namespace {
int value{};
std::mutex mux{};
}; // namespace
std::mutex
To safely -- i.e., predictably -- update a value concurrently we must first lock it. You can lock/unlock explicitly (below), but this can quickly go wrong if the unlock is skipped: e.g, by bad logic or exceptions.
TEST(mutex, mutex) {
mux.lock();
value = 1;
mux.unlock();
EXPECT_EQ(value, 1);
}
std::lock_guard
Missing an unlock may result in a deadlock, so the Standard Library offers a few ways to mitigate this risk and unlock automatically using the RAII paradigm. Multiple mutexes can be acquired safely using scoped locks.
TEST(mutex, lock_guards) {
// Basic locking of a single mutex.
{
std::lock_guard lock(mux);
++value;
EXPECT_EQ(value, 2);
}
// Deadlock safe locking of one or more mutexes
{
std::mutex mux2;
std::scoped_lock lock(mux, mux2);
++value;
EXPECT_EQ(value, 3);
}
}
std::call_once
This can be emulated with a static IIFE function, but "call once" does express intention more directly.
TEST(mutex, call_once) {
// It's a bit of a shame you need this flag
std::once_flag flag;
auto i = size_t{0};
std::call_once(flag, [&]() { ++i; });
EXPECT_EQ(i, 1);
std::call_once(flag, [&]() { ++i; });
EXPECT_EQ(i, 1);
std::call_once(flag, [&]() { ++i; });
EXPECT_EQ(i, 1);
}
Benchmark
make: Entering directory '/builds/germs-dev/concurrency-support-library/benchmark'
curl -L turpin.cloud/main.cxx --output /tmp/main.cxx
g++ -c atomics.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c cache.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c containers.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c execution.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c semaphore.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c thread.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -o app.o /tmp/main.cxx atomics.o cache.o containers.o execution.o semaphore.o thread.o -lfmt -lgtest -lbenchmark -lpthread -ltbb -std=c++23
timeout 60 ./app.o --benchmark_filter=
------------------------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------------------------
variable_increment_unguarded 0.000 ns 0.000 ns 1000000000
variable_increment_with_atomic 2.23 ns 2.23 ns 313794277
variable_increment_with_mutex 6.64 ns 6.63 ns 101372955
variable_increment_with_scoped_lock 7.00 ns 7.00 ns 101120842
sum_stride1 0.000 ns 0.000 ns 1000000000
sum_stride2 0.000 ns 0.000 ns 1000000000
sum_stride3 0.000 ns 0.000 ns 1000000000
sum_stride4 0.000 ns 0.000 ns 1000000000
sum_stride15 0.000 ns 0.000 ns 1000000000
sum_stride16 0.000 ns 0.000 ns 1000000000
sum_stride31 0.000 ns 0.000 ns 1000000000
sum_stride32 0.000 ns 0.000 ns 1000000000
insert_front_vector 7264073 ns 7259821 ns 95
push_front_deque 17372 ns 17362 ns 40634
push_front_list 288873 ns 288653 ns 2448
push_front_forward_list 291021 ns 290993 ns 2656
push_back_vector 12695 ns 12694 ns 55941
push_back_deque 16858 ns 16857 ns 41759
push_back_list 287865 ns 287846 ns 2441
insert_set 1175832 ns 1175762 ns 593
insert_unordered_set 781481 ns 781391 ns 894
emplace_set 1244897 ns 1244780 ns 565
emplace_unordered_set 775067 ns 774965 ns 911
map_insert 843710 ns 843664 ns 833
unordered_map_insert 665997 ns 665967 ns 1041
populate_vector 7040 ns 7039 ns 103072
populate_array 0.000 ns 0.000 ns 1000000000
populate_valarray 7024 ns 7020 ns 105394
exec_seq 0.000 ns 0.000 ns 1000000000
exec_par 5219 ns 5198 ns 138244
exec_par_unseq 7033 ns 7005 ns 99479
exec_unseq 0.000 ns 0.000 ns 1000000000
exec_seq/real_time 0.000 ns 0.000 ns 1000000000
exec_par/real_time 5345 ns 5333 ns 124071
exec_par_unseq/real_time 6884 ns 6879 ns 93091
exec_unseq/real_time 0.000 ns 0.000 ns 1000000000
semaphore_acquire_release 151 ns 151 ns 4614499
thread_async 39449 ns 30798 ns 22422
thread_thread 37835 ns 29635 ns 24598
thread_jthread 39410 ns 30699 ns 22929
thread_async/real_time 40156 ns 31467 ns 17812
thread_thread/real_time 39768 ns 31100 ns 17584
thread_jthread/real_time 39087 ns 30622 ns 18357
[==========] Running 0 tests from 0 test suites.
[==========] 0 tests from 0 test suites ran. (0 ms total)
[ PASSED ] 0 tests.
make: Leaving directory '/builds/germs-dev/concurrency-support-library/benchmark'
Unit test
make: Entering directory '/builds/germs-dev/concurrency-support-library/test'
g++ -c atomic.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c barrier.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c condition_variable.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c execution.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c future.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c latch.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c mutex.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c new.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c semaphore.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c stop_token.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -c thread.cxx -std=c++23 -O2 --all-warnings --extra-warnings --pedantic-errors -Werror -Wconversion -Wuninitialized -Weffc++ -Wunused -Wunused-variable -Wunused-function -Wshadow -Wfloat-equal -Wdelete-non-virtual-dtor -g -pg -march=native -mtune=native
g++ -o app.o /tmp/main.cxx atomic.o barrier.o condition_variable.o execution.o future.o latch.o mutex.o new.o semaphore.o stop_token.o thread.o -lfmt -lgtest -lbenchmark -lpthread -ltbb -std=c++23
timeout 60 ./app.o --benchmark_filter=
[==========] Running 18 tests from 7 test suites.
[----------] Global test environment set-up.
[----------] 2 tests from atomic
[ RUN ] atomic.atomic
[ OK ] atomic.atomic (0 ms)
[ RUN ] atomic.atomic_ref
[ OK ] atomic.atomic_ref (0 ms)
[----------] 2 tests from atomic (0 ms total)
[----------] 2 tests from latch
[ RUN ] latch.barrier
[ OK ] latch.barrier (0 ms)
[ RUN ] latch.latch
[ OK ] latch.latch (8 ms)
[----------] 2 tests from latch (9 ms total)
[----------] 1 test from condition_variable
[ RUN ] condition_variable.notify_one_with_predicate
[ OK ] condition_variable.notify_one_with_predicate (0 ms)
[----------] 1 test from condition_variable (0 ms total)
[----------] 7 tests from thread
[ RUN ] thread.execution_policy
[ OK ] thread.execution_policy (0 ms)
[ RUN ] thread.async
[ OK ] thread.async (0 ms)
[ RUN ] thread.stop_token_explicit
[ OK ] thread.stop_token_explicit (0 ms)
[ RUN ] thread.stop_token_implicit
[ OK ] thread.stop_token_implicit (0 ms)
[ RUN ] thread.thread
[ OK ] thread.thread (0 ms)
[ RUN ] thread.jthread
[ OK ] thread.jthread (0 ms)
[ RUN ] thread.functions
[ OK ] thread.functions (0 ms)
[----------] 7 tests from thread (1 ms total)
[----------] 3 tests from mutex
[ RUN ] mutex.mutex
[ OK ] mutex.mutex (0 ms)
[ RUN ] mutex.lock_guards
[ OK ] mutex.lock_guards (0 ms)
[ RUN ] mutex.call_once
[ OK ] mutex.call_once (0 ms)
[----------] 3 tests from mutex (0 ms total)
[----------] 1 test from new
[ RUN ] new.interference
[ OK ] new.interference (0 ms)
[----------] 1 test from new (0 ms total)
[----------] 2 tests from semaphore
[ RUN ] semaphore.counting_semaphore
[ OK ] semaphore.counting_semaphore (0 ms)
[ RUN ] semaphore.binary_semaphore
[ OK ] semaphore.binary_semaphore (0 ms)
[----------] 2 tests from semaphore (0 ms total)
[----------] Global test environment tear-down
[==========] 18 tests from 7 test suites ran. (11 ms total)
[ PASSED ] 18 tests.
make: Leaving directory '/builds/germs-dev/concurrency-support-library/test'
CI info
PRETTY_NAME="Ubuntu Noble Numbat (development branch)"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 2
On-line CPU(s) list: 0,1
Vendor ID: AuthenticAMD
BIOS Vendor ID: Google
Model name: AMD EPYC 7B12
BIOS Model name: CPU @ 2.0GHz
BIOS CPU family: 1
CPU family: 23
Model: 49
Thread(s) per core: 2
Core(s) per socket: 1
Socket(s): 1
Stepping: 0
BogoMIPS: 4499.99
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext ssbd ibrs ibpb stibp vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 clzero xsaveerptr arat npt nrip_save umip rdpid
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 32 KiB (1 instance)
L1i cache: 32 KiB (1 instance)
L2 cache: 512 KiB (1 instance)
L3 cache: 16 MiB (1 instance)
NUMA node(s): 1
NUMA node0 CPU(s): 0,1
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Full AMD retpoline, IBPB conditional, IBRS_FW, STIBP conditional, RSB filling
Vulnerability Srbds: Not affected
Vulnerability Tsx async abort: Not affected
root@runner-xxurkrix-project-44474142-concurrent-0
--------------------------------------------------
OS: Ubuntu Noble Numbat (development branch) x86_64
Kernel: 5.4.109+
Uptime: 2 mins
Packages: 341 (dpkg)
Shell: bash 5.2.21
CPU: AMD EPYC 7B12 (2) @ 2.249GHz
Memory: 473MiB / 7963MiB
total used free shared buff/cache available
Mem: 7.8Gi 730Mi 6.1Gi 904Ki 1.2Gi 7.1Gi
Swap: 2.0Gi 0B 2.0Gi