2
0
mirror of https://github.com/boostorg/atomic.git synced 2026-01-19 04:02:09 +00:00

Switched tests to std::thread and std::chrono.

This removes dependencies on Boost.Thread and Boost.Chrono, as well as
their dependencies and potentially allows to test more compilers. In particular,
this removes the dependency on Boost.Lexical cast, which no longer compiles
with gcc 4.6 and 4.7.
This commit is contained in:
Andrey Semashev
2023-09-03 20:16:16 +03:00
parent 8f480549ee
commit aebe9d585c
12 changed files with 366 additions and 219 deletions

View File

@@ -40,6 +40,8 @@ jobs:
- toolset: gcc-4.6
cxxstd64: "0x"
cxxstd32: "0x"
# Workaround for missing std::this_thread::sleep_* functions
cxxflags: -D_GLIBCXX_USE_NANOSLEEP
os: ubuntu-latest
container: ubuntu:16.04
install:
@@ -51,6 +53,8 @@ jobs:
- toolset: gcc-4.7
cxxstd64: "11"
cxxstd32: "11"
# Workaround for missing std::this_thread::sleep_* functions
cxxflags: -D_GLIBCXX_USE_NANOSLEEP
os: ubuntu-latest
container: ubuntu:16.04
install:

View File

@@ -2,7 +2,7 @@
#
# Copyright (c) 2011 Helge Bahmann
# Copyright (c) 2012 Tim Blechmann
# Copyright (c) 2020 Andrey Semashev
# Copyright (c) 2020-2023 Andrey Semashev
#
# Distributed under the Boost Software License, Version 1.0. (See
# accompanying file LICENSE_1_0.txt or copy at
@@ -14,8 +14,6 @@ project boost/atomic/test
: requirements
<include>.
<threading>multi
<library>/boost/chrono//boost_chrono
<library>/boost/thread//boost_thread
<library>/boost/atomic//boost_atomic
<target-os>windows:<define>BOOST_USE_WINDOWS_H
<toolset>gcc,<target-os>windows:<linkflags>"-lkernel32"

View File

@@ -24,22 +24,18 @@
// operations truly behave atomic if this test program does not
// report an error.
#include <boost/atomic.hpp>
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <boost/config.hpp>
#include <boost/ref.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/core/lightweight_test.hpp>
#include "test_clock.hpp"
/* helper class to let two instances of a function race against each
other, with configurable timeout and early abort on detection of error */
@@ -49,29 +45,32 @@ public:
/* concurrently run the function in two threads, until either timeout
or one of the functions returns "false"; returns true if timeout
was reached, or false if early abort and updates timeout accordingly */
static bool execute(const boost::function<bool(std::size_t)> & fn, boost::posix_time::time_duration & timeout)
static bool execute(std::function< bool (std::size_t) > const& fn, steady_clock::duration& timeout)
{
concurrent_runner runner(fn);
runner.wait_finish(timeout);
return !runner.failure();
}
concurrent_runner(const boost::function<bool(std::size_t)> & fn) :
concurrent_runner(std::function< bool (std::size_t) > const& fn) :
finished_(false), failure_(false)
{
boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 0)).swap(first_thread_);
boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 1)).swap(second_thread_);
first_thread_ = std::thread([this, fn]() { thread_function(fn, 0); });
second_thread_ = std::thread([this, fn]() { thread_function(fn, 1); });
}
void wait_finish(boost::posix_time::time_duration & timeout)
void wait_finish(steady_clock::duration& timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
steady_clock::time_point start = steady_clock::now();
steady_clock::time_point end = start + timeout;
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !finished())
c_.timed_wait(guard, end);
std::unique_lock< std::mutex > guard(m_);
while (!finished())
{
if (c_.wait_until(guard, end) == std::cv_status::timeout)
break;
}
}
finished_.store(true, boost::memory_order_relaxed);
@@ -79,7 +78,7 @@ public:
first_thread_.join();
second_thread_.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
steady_clock::duration duration = steady_clock::now() - start;
if (duration < timeout)
timeout = duration;
}
@@ -95,13 +94,13 @@ public:
}
private:
void thread_function(boost::function<bool(std::size_t)> function, std::size_t instance)
void thread_function(std::function< bool (std::size_t) > const& function, std::size_t instance)
{
while (!finished())
{
if (!function(instance))
{
boost::lock_guard< boost::mutex > guard(m_);
std::lock_guard< std::mutex > guard(m_);
failure_ = true;
finished_.store(true, boost::memory_order_relaxed);
c_.notify_all();
@@ -111,17 +110,17 @@ private:
}
private:
boost::mutex m_;
boost::condition_variable c_;
std::mutex m_;
std::condition_variable c_;
boost::atomic<bool> finished_;
bool failure_;
boost::thread first_thread_;
boost::thread second_thread_;
std::thread first_thread_;
std::thread second_thread_;
};
bool racy_add(volatile unsigned int & value, std::size_t instance)
bool racy_add(unsigned int volatile& value, std::size_t instance)
{
std::size_t shift = instance * 8;
unsigned int mask = 0xff << shift;
@@ -150,11 +149,11 @@ double estimate_avg_race_time(void)
/* take 10 samples */
for (std::size_t n = 0; n < 10; ++n)
{
boost::posix_time::time_duration timeout(0, 0, 10);
steady_clock::duration timeout = std::chrono::seconds(10);
volatile unsigned int value(0);
bool success = concurrent_runner::execute(
boost::bind(racy_add, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return racy_add(value, instance); },
timeout
);
@@ -163,7 +162,7 @@ double estimate_avg_race_time(void)
BOOST_ERROR("Failed to establish baseline time for reproducing race condition");
}
sum = sum + timeout.total_microseconds();
sum += std::chrono::duration_cast< std::chrono::microseconds >(timeout).count();
}
/* determine maximum likelihood estimate for average time between
@@ -177,7 +176,7 @@ double estimate_avg_race_time(void)
}
template<typename value_type, std::size_t shift_>
bool test_arithmetic(boost::atomic<value_type> & shared_value, std::size_t instance)
bool test_arithmetic(boost::atomic< value_type >& shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
@@ -204,7 +203,7 @@ bool test_arithmetic(boost::atomic<value_type> & shared_value, std::size_t insta
}
template<typename value_type, std::size_t shift_>
bool test_bitops(boost::atomic<value_type> & shared_value, std::size_t instance)
bool test_bitops(boost::atomic< value_type >& shared_value, std::size_t instance)
{
std::size_t shift = instance * 8;
value_type mask = 0xff << shift;
@@ -247,17 +246,17 @@ int main(int, char *[])
double avg_race_time = estimate_avg_race_time();
/* 5.298 = 0.995 quantile of exponential distribution */
const boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time));
const steady_clock::duration timeout = std::chrono::microseconds(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time));
{
boost::atomic<unsigned int> value(0);
/* testing two different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 2);
steady_clock::duration tmp(timeout * 2);
bool success = concurrent_runner::execute(
boost::bind(test_arithmetic<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return test_arithmetic< unsigned int, 0 >(value, instance); },
tmp
);
@@ -269,10 +268,10 @@ int main(int, char *[])
/* testing three different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 3);
steady_clock::duration tmp(timeout * 3);
bool success = concurrent_runner::execute(
boost::bind(test_bitops<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return test_bitops< unsigned int, 0 >(value, instance); },
tmp
);

View File

@@ -32,19 +32,14 @@
#include <boost/atomic/atomic_ref.hpp>
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <boost/config.hpp>
#include <boost/ref.hpp>
#include <boost/function.hpp>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/core/lightweight_test.hpp>
#include "test_clock.hpp"
/* helper class to let two instances of a function race against each
other, with configurable timeout and early abort on detection of error */
@@ -54,30 +49,32 @@ public:
/* concurrently run the function in two threads, until either timeout
or one of the functions returns "false"; returns true if timeout
was reached, or false if early abort and updates timeout accordingly */
static bool execute(const boost::function<bool(std::size_t)> & fn, boost::posix_time::time_duration & timeout)
static bool execute(std::function< bool (std::size_t) > const& fn, steady_clock::duration& timeout)
{
concurrent_runner runner(fn);
runner.wait_finish(timeout);
return !runner.failure();
}
concurrent_runner(const boost::function<bool(std::size_t)> & fn) :
concurrent_runner(std::function< bool (std::size_t) > const& fn) :
finished_(false), failure_(false)
{
boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 0)).swap(first_thread_);
boost::thread(boost::bind(&concurrent_runner::thread_function, this, fn, 1)).swap(second_thread_);
first_thread_ = std::thread([this, fn]() { thread_function(fn, 0); });
second_thread_ = std::thread([this, fn]() { thread_function(fn, 1); });
}
void wait_finish(boost::posix_time::time_duration & timeout)
void wait_finish(steady_clock::duration& timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
steady_clock::time_point start = steady_clock::now();
steady_clock::time_point end = start + timeout;
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !finished())
c_.timed_wait(guard, end);
std::unique_lock< std::mutex > guard(m_);
while (!finished())
{
if (c_.wait_until(guard, end) == std::cv_status::timeout)
break;
}
}
finished_.store(true, boost::memory_order_relaxed);
@@ -85,7 +82,7 @@ public:
first_thread_.join();
second_thread_.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
steady_clock::duration duration = steady_clock::now() - start;
if (duration < timeout)
timeout = duration;
}
@@ -101,13 +98,13 @@ public:
}
private:
void thread_function(boost::function<bool(std::size_t)> function, std::size_t instance)
void thread_function(std::function< bool (std::size_t) > const& function, std::size_t instance)
{
while (!finished())
{
if (!function(instance))
{
boost::lock_guard< boost::mutex > guard(m_);
std::lock_guard< std::mutex > guard(m_);
failure_ = true;
finished_.store(true, boost::memory_order_relaxed);
c_.notify_all();
@@ -117,17 +114,17 @@ private:
}
private:
boost::mutex m_;
boost::condition_variable c_;
std::mutex m_;
std::condition_variable c_;
boost::atomic<bool> finished_;
bool failure_;
boost::thread first_thread_;
boost::thread second_thread_;
std::thread first_thread_;
std::thread second_thread_;
};
bool racy_add(volatile unsigned int & value, std::size_t instance)
bool racy_add(unsigned int volatile& value, std::size_t instance)
{
std::size_t shift = instance * 8;
unsigned int mask = 0xff << shift;
@@ -154,13 +151,13 @@ double estimate_avg_race_time(void)
double sum = 0.0;
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
for (std::size_t n = 0; n < 10; ++n)
{
boost::posix_time::time_duration timeout(0, 0, 10);
steady_clock::duration timeout = std::chrono::seconds(10);
volatile unsigned int value(0);
bool success = concurrent_runner::execute(
boost::bind(racy_add, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return racy_add(value, instance); },
timeout
);
@@ -169,7 +166,7 @@ double estimate_avg_race_time(void)
BOOST_ERROR("Failed to establish baseline time for reproducing race condition");
}
sum = sum + timeout.total_microseconds();
sum += std::chrono::duration_cast< std::chrono::microseconds >(timeout).count();
}
/* determine maximum likelihood estimate for average time between
@@ -255,17 +252,17 @@ int main(int, char *[])
double avg_race_time = estimate_avg_race_time();
/* 5.298 = 0.995 quantile of exponential distribution */
const boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time));
const steady_clock::duration timeout = std::chrono::microseconds(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time));
{
unsigned int value = 0;
/* testing two different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 2);
steady_clock::duration tmp(timeout * 2);
bool success = concurrent_runner::execute(
boost::bind(test_arithmetic<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return test_arithmetic< unsigned int, 0 >(value, instance); },
tmp
);
@@ -277,10 +274,10 @@ int main(int, char *[])
/* testing three different operations in this loop, therefore
enlarge timeout */
boost::posix_time::time_duration tmp(timeout * 3);
steady_clock::duration tmp(timeout * 3);
bool success = concurrent_runner::execute(
boost::bind(test_bitops<unsigned int, 0>, boost::ref(value), boost::placeholders::_1),
[&value](std::size_t instance) { return test_bitops< unsigned int, 0 >(value, instance); },
tmp
);

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Andrey Semashev
// Copyright (c) 2020-2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
@@ -12,20 +12,20 @@
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <thread>
#include <memory>
#include <utility>
#include <iostream>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/atomic/capabilities.hpp>
#include <boost/atomic/ipc_atomic_flag.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/smart_ptr/scoped_ptr.hpp>
#include "atomic_wrapper.hpp"
#include "lightweight_test_stream.hpp"
#include "test_clock.hpp"
#include "test_thread.hpp"
#include "test_barrier.hpp"
//! Since some of the tests below are allowed to fail, we retry up to this many times to pass the test
BOOST_CONSTEXPR_OR_CONST unsigned int test_retry_count = 5u;
@@ -66,7 +66,7 @@ private:
T m_value1, m_value2, m_value3;
boost::barrier m_barrier;
test_barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
@@ -85,19 +85,19 @@ public:
bool run()
{
boost::thread thread1(&notify_one_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_one_test::thread_func, this, &m_thread2_state);
test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); });
test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); });
m_barrier.wait();
m_barrier.arrive_and_wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_one();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value3, boost::memory_order_release);
m_wrapper.a.notify_one();
@@ -154,7 +154,7 @@ public:
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
m_barrier.arrive_and_wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
@@ -167,7 +167,7 @@ inline void test_notify_one(T value1, T value2, T value3)
for (unsigned int i = 0u; i < test_retry_count; ++i)
{
// Avoid creating IPC atomics on the stack as this breaks on Darwin
boost::scoped_ptr< notify_one_test< Wrapper, T > > test(new notify_one_test< Wrapper, T >(value1, value2, value3));
std::unique_ptr< notify_one_test< Wrapper, T > > test(new notify_one_test< Wrapper, T >(value1, value2, value3));
if (test->run())
return;
}
@@ -201,7 +201,7 @@ private:
T m_value1, m_value2;
boost::barrier m_barrier;
test_barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
@@ -219,14 +219,14 @@ public:
bool run()
{
boost::thread thread1(&notify_all_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_all_test::thread_func, this, &m_thread2_state);
test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); });
test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); });
m_barrier.wait();
m_barrier.arrive_and_wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_all();
@@ -266,7 +266,7 @@ public:
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
m_barrier.arrive_and_wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
@@ -279,7 +279,7 @@ inline void test_notify_all(T value1, T value2)
for (unsigned int i = 0u; i < test_retry_count; ++i)
{
// Avoid creating IPC atomics on the stack as this breaks on Darwin
boost::scoped_ptr< notify_all_test< Wrapper, T > > test(new notify_all_test< Wrapper, T >(value1, value2));
std::unique_ptr< notify_all_test< Wrapper, T > > test(new notify_all_test< Wrapper, T >(value1, value2));
if (test->run())
return;
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2011 Helge Bahmann
// Copyright (c) 2012 Tim Blechmann
// Copyright (c) 2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
@@ -27,19 +28,18 @@
// fences work as expected if this test program does not
// report an error.
#include <boost/atomic.hpp>
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <cstddef>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/barrier.hpp>
#include <cstdlib>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <boost/core/lightweight_test.hpp>
#include "test_clock.hpp"
#include "test_barrier.hpp"
// Two threads perform the following operations:
//
@@ -57,7 +57,7 @@ class total_store_order_test
public:
total_store_order_test(void);
void run(boost::posix_time::time_duration & timeout);
void run(steady_clock::duration& timeout);
bool detected_conflict(void) const { return detected_conflict_; }
private:
@@ -74,7 +74,7 @@ private:
boost::atomic<int> b_;
char pad2_[512];
boost::barrier barrier_;
test_barrier barrier_;
int vrfyb1_, vrfya2_;
@@ -82,8 +82,8 @@ private:
boost::atomic<int> termination_consensus_;
bool detected_conflict_;
boost::mutex m_;
boost::condition_variable c_;
std::mutex m_;
std::condition_variable c_;
};
template<boost::memory_order store_order, boost::memory_order load_order>
@@ -96,18 +96,21 @@ total_store_order_test<store_order, load_order>::total_store_order_test(void) :
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::run(boost::posix_time::time_duration & timeout)
void total_store_order_test<store_order, load_order>::run(steady_clock::duration& timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
steady_clock::time_point start = steady_clock::now();
steady_clock::time_point end = start + timeout;
boost::thread t1(boost::bind(&total_store_order_test::thread1fn, this));
boost::thread t2(boost::bind(&total_store_order_test::thread2fn, this));
std::thread t1([this]() { this->thread1fn(); });
std::thread t2([this]() { this->thread2fn(); });
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !detected_conflict_)
c_.timed_wait(guard, end);
std::unique_lock< std::mutex > lock(m_);
while (!detected_conflict_)
{
if (c_.wait_until(lock, end) == std::cv_status::timeout)
break;
}
}
terminate_threads_.store(true, boost::memory_order_relaxed);
@@ -115,7 +118,7 @@ void total_store_order_test<store_order, load_order>::run(boost::posix_time::tim
t2.join();
t1.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
steady_clock::duration duration = steady_clock::now() - start;
if (duration < timeout)
timeout = duration;
}
@@ -130,11 +133,11 @@ void total_store_order_test<store_order, load_order>::thread1fn(void)
a_.store(1, store_order);
int b = b_.load(load_order);
barrier_.wait();
barrier_.arrive_and_wait();
vrfyb1_ = b;
barrier_.wait();
barrier_.arrive_and_wait();
check_conflict();
@@ -156,10 +159,10 @@ void total_store_order_test<store_order, load_order>::thread1fn(void)
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
unsigned int delay = std::rand() % 10000;
a_.store(0, boost::memory_order_relaxed);
barrier_.wait();
barrier_.arrive_and_wait();
while (delay--)
backoff_dummy = delay;
@@ -174,11 +177,11 @@ void total_store_order_test<store_order, load_order>::thread2fn(void)
b_.store(1, store_order);
int a = a_.load(load_order);
barrier_.wait();
barrier_.arrive_and_wait();
vrfya2_ = a;
barrier_.wait();
barrier_.arrive_and_wait();
check_conflict();
@@ -200,10 +203,10 @@ void total_store_order_test<store_order, load_order>::thread2fn(void)
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
unsigned int delay = std::rand() % 10000;
b_.store(0, boost::memory_order_relaxed);
barrier_.wait();
barrier_.arrive_and_wait();
while (delay--)
backoff_dummy = delay;
@@ -215,7 +218,7 @@ void total_store_order_test<store_order, load_order>::check_conflict(void)
{
if (vrfyb1_ == 0 && vrfya2_ == 0)
{
boost::lock_guard< boost::mutex > guard(m_);
std::lock_guard< std::mutex > guard(m_);
detected_conflict_ = true;
terminate_threads_.store(true, boost::memory_order_relaxed);
c_.notify_all();
@@ -229,7 +232,7 @@ void test_seq_cst(void)
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
{
boost::posix_time::time_duration timeout(0, 0, 10);
steady_clock::duration timeout = std::chrono::seconds(10);
total_store_order_test<boost::memory_order_relaxed, boost::memory_order_relaxed> test;
test.run(timeout);
@@ -239,9 +242,10 @@ void test_seq_cst(void)
return;
}
std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n";
std::chrono::microseconds timeout_us = std::chrono::duration_cast< std::chrono::microseconds >(timeout);
std::cout << "seq_cst violation with order=relaxed after " << timeout_us.count() << " us\n";
sum = sum + timeout.total_microseconds();
sum += timeout_us.count();
}
/* determine maximum likelihood estimate for average time between
@@ -252,9 +256,10 @@ void test_seq_cst(void)
double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
/* 5.298 = 0.995 quantile of exponential distribution */
boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time_995));
std::chrono::microseconds timeout_us(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time_995));
steady_clock::duration timeout = timeout_us;
std::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n";
std::cout << "run seq_cst for " << timeout_us.count() << " us\n";
total_store_order_test<boost::memory_order_seq_cst, boost::memory_order_seq_cst> test;
test.run(timeout);

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Andrey Semashev
// Copyright (c) 2020-2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
@@ -34,16 +34,14 @@
#include <boost/atomic/atomic_ref.hpp>
#include <cstddef>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/thread_time.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/lock_types.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/barrier.hpp>
#include <cstdlib>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <boost/core/lightweight_test.hpp>
#include "test_clock.hpp"
#include "test_barrier.hpp"
// Two threads perform the following operations:
//
@@ -61,7 +59,7 @@ class total_store_order_test
public:
total_store_order_test(void);
void run(boost::posix_time::time_duration & timeout);
void run(steady_clock::duration& timeout);
bool detected_conflict(void) const { return detected_conflict_; }
private:
@@ -80,7 +78,7 @@ private:
boost::atomic_ref<int> b_;
char pad2_[512];
boost::barrier barrier_;
test_barrier barrier_;
int vrfyb1_, vrfya2_;
@@ -88,8 +86,8 @@ private:
boost::atomic<int> termination_consensus_;
bool detected_conflict_;
boost::mutex m_;
boost::condition_variable c_;
std::mutex m_;
std::condition_variable c_;
};
template<boost::memory_order store_order, boost::memory_order load_order>
@@ -102,18 +100,21 @@ total_store_order_test<store_order, load_order>::total_store_order_test(void) :
}
template<boost::memory_order store_order, boost::memory_order load_order>
void total_store_order_test<store_order, load_order>::run(boost::posix_time::time_duration & timeout)
void total_store_order_test<store_order, load_order>::run(steady_clock::duration& timeout)
{
boost::system_time start = boost::get_system_time();
boost::system_time end = start + timeout;
steady_clock::time_point start = steady_clock::now();
steady_clock::time_point end = start + timeout;
boost::thread t1(boost::bind(&total_store_order_test::thread1fn, this));
boost::thread t2(boost::bind(&total_store_order_test::thread2fn, this));
std::thread t1([this]() { this->thread1fn(); });
std::thread t2([this]() { this->thread2fn(); });
{
boost::unique_lock< boost::mutex > guard(m_);
while (boost::get_system_time() < end && !detected_conflict_)
c_.timed_wait(guard, end);
std::unique_lock< std::mutex > lock(m_);
while (!detected_conflict_)
{
if (c_.wait_until(lock, end) == std::cv_status::timeout)
break;
}
}
terminate_threads_.store(true, boost::memory_order_relaxed);
@@ -121,7 +122,7 @@ void total_store_order_test<store_order, load_order>::run(boost::posix_time::tim
t2.join();
t1.join();
boost::posix_time::time_duration duration = boost::get_system_time() - start;
steady_clock::duration duration = steady_clock::now() - start;
if (duration < timeout)
timeout = duration;
}
@@ -136,11 +137,11 @@ void total_store_order_test<store_order, load_order>::thread1fn(void)
a_.store(1, store_order);
int b = b_.load(load_order);
barrier_.wait();
barrier_.arrive_and_wait();
vrfyb1_ = b;
barrier_.wait();
barrier_.arrive_and_wait();
check_conflict();
@@ -162,10 +163,10 @@ void total_store_order_test<store_order, load_order>::thread1fn(void)
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
unsigned int delay = std::rand() % 10000;
a_.store(0, boost::memory_order_relaxed);
barrier_.wait();
barrier_.arrive_and_wait();
while (delay--)
backoff_dummy = delay;
@@ -180,11 +181,11 @@ void total_store_order_test<store_order, load_order>::thread2fn(void)
b_.store(1, store_order);
int a = a_.load(load_order);
barrier_.wait();
barrier_.arrive_and_wait();
vrfya2_ = a;
barrier_.wait();
barrier_.arrive_and_wait();
check_conflict();
@@ -206,10 +207,10 @@ void total_store_order_test<store_order, load_order>::thread2fn(void)
termination_consensus_.fetch_xor(4, boost::memory_order_relaxed);
unsigned int delay = rand() % 10000;
unsigned int delay = std::rand() % 10000;
b_.store(0, boost::memory_order_relaxed);
barrier_.wait();
barrier_.arrive_and_wait();
while (delay--)
backoff_dummy = delay;
@@ -221,7 +222,7 @@ void total_store_order_test<store_order, load_order>::check_conflict(void)
{
if (vrfyb1_ == 0 && vrfya2_ == 0)
{
boost::lock_guard< boost::mutex > guard(m_);
std::lock_guard< std::mutex > guard(m_);
detected_conflict_ = true;
terminate_threads_.store(true, boost::memory_order_relaxed);
c_.notify_all();
@@ -235,7 +236,7 @@ void test_seq_cst(void)
/* take 10 samples */
for (std::size_t n = 0; n < 10; n++)
{
boost::posix_time::time_duration timeout(0, 0, 10);
steady_clock::duration timeout = std::chrono::seconds(10);
total_store_order_test<boost::memory_order_relaxed, boost::memory_order_relaxed> test;
test.run(timeout);
@@ -245,9 +246,10 @@ void test_seq_cst(void)
return;
}
std::cout << "seq_cst violation with order=relaxed after " << timeout.total_microseconds() << " us\n";
std::chrono::microseconds timeout_us = std::chrono::duration_cast< std::chrono::microseconds >(timeout);
std::cout << "seq_cst violation with order=relaxed after " << timeout_us.count() << " us\n";
sum = sum + timeout.total_microseconds();
sum += timeout_us.count();
}
/* determine maximum likelihood estimate for average time between
@@ -258,9 +260,10 @@ void test_seq_cst(void)
double avg_race_time_995 = avg_race_time_mle * 2 * 10 / 7.44;
/* 5.298 = 0.995 quantile of exponential distribution */
boost::posix_time::time_duration timeout = boost::posix_time::microseconds((long)(5.298 * avg_race_time_995));
std::chrono::microseconds timeout_us(static_cast< std::chrono::microseconds::rep >(5.298 * avg_race_time_995));
steady_clock::duration timeout = timeout_us;
std::cout << "run seq_cst for " << timeout.total_microseconds() << " us\n";
std::cout << "run seq_cst for " << timeout_us.count() << " us\n";
total_store_order_test<boost::memory_order_seq_cst, boost::memory_order_seq_cst> test;
test.run(timeout);

54
test/test_barrier.hpp Normal file
View File

@@ -0,0 +1,54 @@
// Copyright (c) 2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_
#include <mutex>
#include <condition_variable>
//! A simplified version of thread barrier from Boost.Thread and C++20 std::barrier
class test_barrier
{
private:
std::mutex m_mutex;
std::condition_variable m_cond;
unsigned int m_generation;
unsigned int m_count;
const unsigned int m_initial_count;
public:
explicit test_barrier(unsigned int initial_count) :
m_generation(0u), m_count(initial_count), m_initial_count(initial_count)
{
}
test_barrier(test_barrier const&) = delete;
test_barrier& operator= (test_barrier const&) = delete;
void arrive_and_wait()
{
std::unique_lock< std::mutex > lock(m_mutex);
--m_count;
if (m_count == 0u)
{
++m_generation;
m_count = m_initial_count;
m_cond.notify_all();
return;
}
const unsigned int generation = m_generation;
do
{
m_cond.wait(lock);
}
while (m_generation == generation);
}
};
#endif // BOOST_ATOMIC_TEST_BARRIER_HPP_INCLUDED_

View File

@@ -12,11 +12,17 @@
#include <boost/winapi/config.hpp>
#include <boost/winapi/basic_types.hpp>
#include <boost/winapi/time.hpp>
#include <boost/ratio/ratio.hpp>
#include <ratio>
#endif
#include <boost/chrono/chrono.hpp>
#include <chrono>
namespace chrono = boost::chrono;
namespace chrono = std::chrono;
#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 40700
typedef chrono::monotonic_clock steady_clock;
#else
typedef chrono::steady_clock steady_clock;
#endif
#if defined(BOOST_WINDOWS)
@@ -29,7 +35,7 @@ struct test_clock
#else
typedef boost::winapi::DWORD_ rep;
#endif
typedef boost::milli period;
typedef std::milli period;
typedef chrono::duration< rep, period > duration;
typedef chrono::time_point< test_clock, duration > time_point;
@@ -46,10 +52,8 @@ struct test_clock
}
};
#elif defined(BOOST_CHRONO_HAS_CLOCK_STEADY)
typedef chrono::steady_clock test_clock;
#else
typedef chrono::system_clock test_clock;
typedef steady_clock test_clock;
#endif
#endif // BOOST_ATOMIC_TEST_TEST_CLOCK_HPP_INCLUDED_

84
test/test_thread.hpp Normal file
View File

@@ -0,0 +1,84 @@
// Copyright (c) 2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_
#define BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include "test_clock.hpp"
//! Test thread class with the ability to join the thread with a timeout
class test_thread
{
private:
std::mutex m_mutex;
std::condition_variable m_cond;
bool m_finished;
std::thread m_thread;
public:
template< typename Func >
explicit test_thread(Func&& func) :
m_finished(false),
m_thread([this, func]()
{
try
{
func();
}
catch (...)
{
mark_finished();
throw;
}
mark_finished();
})
{
}
test_thread(test_thread const&) = delete;
test_thread& operator= (test_thread const&) = delete;
void join()
{
m_thread.join();
}
template< typename Rep, typename Period >
bool try_join_for(std::chrono::duration< Rep, Period > dur)
{
return try_join_until(steady_clock::now() + dur);
}
template< typename Clock, typename Duration >
bool try_join_until(std::chrono::time_point< Clock, Duration > timeout)
{
{
std::unique_lock< std::mutex > lock(m_mutex);
while (!m_finished)
{
if (m_cond.wait_until(lock, timeout) == std::cv_status::timeout)
return false;
}
}
join();
return true;
}
private:
void mark_finished()
{
std::lock_guard< std::mutex > lock(m_mutex);
m_finished = true;
m_cond.notify_all();
}
};
#endif // BOOST_ATOMIC_TEST_THREAD_HPP_INCLUDED_

View File

@@ -13,23 +13,21 @@
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <chrono>
#include <thread>
#include <memory>
#include <iostream>
#include <boost/config.hpp>
#include <boost/bind/bind.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/smart_ptr/scoped_array.hpp>
namespace chrono = boost::chrono;
#include "test_clock.hpp"
#include "test_barrier.hpp"
boost::atomic< unsigned int > g_atomic(0u);
BOOST_CONSTEXPR_OR_CONST unsigned int loop_count = 4096u;
void thread_func(boost::barrier* barrier)
void thread_func(test_barrier* barrier)
{
barrier->wait();
barrier->arrive_and_wait();
unsigned int old_count = 0u;
while (true)
@@ -44,17 +42,17 @@ void thread_func(boost::barrier* barrier)
int main()
{
const unsigned int thread_count = boost::thread::hardware_concurrency() + 4u;
boost::barrier barrier(thread_count + 1u);
boost::scoped_array< boost::thread > threads(new boost::thread[thread_count]);
const unsigned int thread_count = std::thread::hardware_concurrency() + 4u;
test_barrier barrier(thread_count + 1u);
std::unique_ptr< std::thread[] > threads(new std::thread[thread_count]);
for (unsigned int i = 0u; i < thread_count; ++i)
boost::thread(boost::bind(&thread_func, &barrier)).swap(threads[i]);
threads[i] = std::thread([&barrier]() { thread_func(&barrier); });
barrier.wait();
barrier.arrive_and_wait();
// Let the threads block on the atomic counter
boost::this_thread::sleep_for(chrono::milliseconds(100));
std::this_thread::sleep_for(chrono::milliseconds(100));
while (true)
{

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Andrey Semashev
// Copyright (c) 2020-2023 Andrey Semashev
//
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE_1_0.txt or copy at
@@ -12,16 +12,17 @@
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <thread>
#include <memory>
#include <utility>
#include <iostream>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include "atomic_wrapper.hpp"
#include "lightweight_test_stream.hpp"
#include "test_clock.hpp"
#include "test_thread.hpp"
#include "test_barrier.hpp"
//! Since some of the tests below are allowed to fail, we retry up to this many times to pass the test
BOOST_CONSTEXPR_OR_CONST unsigned int test_retry_count = 5u;
@@ -62,7 +63,7 @@ private:
T m_value1, m_value2, m_value3;
boost::barrier m_barrier;
test_barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
@@ -81,19 +82,19 @@ public:
bool run()
{
boost::thread thread1(&notify_one_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_one_test::thread_func, this, &m_thread2_state);
test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); });
test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); });
m_barrier.wait();
m_barrier.arrive_and_wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_one();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value3, boost::memory_order_release);
m_wrapper.a.notify_one();
@@ -141,7 +142,7 @@ public:
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
m_barrier.arrive_and_wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();
@@ -187,7 +188,7 @@ private:
T m_value1, m_value2;
boost::barrier m_barrier;
test_barrier m_barrier;
thread_state m_thread1_state;
thread_state m_thread2_state;
@@ -205,14 +206,14 @@ public:
bool run()
{
boost::thread thread1(&notify_all_test::thread_func, this, &m_thread1_state);
boost::thread thread2(&notify_all_test::thread_func, this, &m_thread2_state);
test_thread thread1([this]() { this->thread_func(&this->m_thread1_state); });
test_thread thread2([this]() { this->thread_func(&this->m_thread2_state); });
m_barrier.wait();
m_barrier.arrive_and_wait();
test_clock::time_point start_time = test_clock::now();
boost::this_thread::sleep_for(chrono::milliseconds(200));
std::this_thread::sleep_for(chrono::milliseconds(200));
m_wrapper.a.store(m_value2, boost::memory_order_release);
m_wrapper.a.notify_all();
@@ -249,7 +250,7 @@ public:
private:
void thread_func(thread_state* state)
{
m_barrier.wait();
m_barrier.arrive_and_wait();
state->m_received_value = m_wrapper.a.wait(m_value1);
state->m_wakeup_time = test_clock::now();