Preparing to use C++20 atomics for thread locking, which is slightly more efficient. Right now, that's an experimental option.

This commit is contained in:
Matthias Koefferlein 2026-02-19 21:36:00 +01:00
parent a8119c5a53
commit 9534209240
7 changed files with 102 additions and 535 deletions

View File

@ -41,6 +41,7 @@ HAVE_CURL=0
HAVE_EXPAT=0
HAVE_GIT2=1
HAVE_LSTREAM=1
HAVE_CPP20=0
RUBYINCLUDE=""
RUBYINCLUDE2=""
@ -217,6 +218,9 @@ while [ "$*" != "" ]; do
-nolstream)
HAVE_LSTREAM=0
;;
-cpp20)
HAVE_CPP20=1
;;
-qt5)
echo "*** WARNING: -qt5 option is ignored - Qt version is auto-detected now."
;;
@ -275,6 +279,7 @@ while [ "$*" != "" ]; do
echo " -libpng Use libpng instead of Qt for PNG generation"
echo " -nolibgit2 Do not include libgit2 for Git package support"
echo " -nolstream Do not include the LStream plugin"
echo " -cpp20 Uses some C++20 features (e.g. atomics)"
echo ""
echo "Environment Variables:"
echo ""
@ -670,6 +675,7 @@ qmake_options=(
HAVE_PNG="$HAVE_PNG"
HAVE_GIT2="$HAVE_GIT2"
HAVE_LSTREAM="$HAVE_LSTREAM"
HAVE_CPP20="$HAVE_CPP20"
PREFIX="$BIN"
RPATH="$RPATH"
KLAYOUT_VERSION="$KLAYOUT_VERSION"

View File

@ -204,12 +204,16 @@ msvc {
# -Wno-reserved-user-defined-literal \
#
lessThan(QT_MAJOR_VERSION, 6) {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++11
equals(HAVE_CPP20, "1") {
QMAKE_CXXFLAGS += -std=c++20
DEFINES += HAVE_CPP20
} else {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++17
lessThan(QT_MAJOR_VERSION, 6) {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++11
} else {
QMAKE_CXXFLAGS += -std=c++17
}
}
win32 {

View File

@ -1,203 +0,0 @@
//----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_ATOMIC_H_
#define ATOMIC_ATOMIC_H_
// Macro for disallowing copying of an object.
#if __cplusplus >= 201103L
#define ATOMIC_DISALLOW_COPY(T) \
T(const T&) = delete; \
T& operator=(const T&) = delete;
#else
#define ATOMIC_DISALLOW_COPY(T) \
T(const T&); \
T& operator=(const T&);
#endif
// A portable static assert.
#if __cplusplus >= 201103L
#define ATOMIC_STATIC_ASSERT(condition, message) \
static_assert((condition), message)
#else
// Based on: http://stackoverflow.com/a/809465/5778708
#define ATOMIC_STATIC_ASSERT(condition, message) \
_impl_STATIC_ASSERT_LINE(condition, __LINE__)
#define _impl_PASTE(a, b) a##b
#ifdef __GNUC__
#define _impl_UNUSED __attribute__((__unused__))
#else
#define _impl_UNUSED
#endif
#define _impl_STATIC_ASSERT_LINE(condition, line) \
typedef char _impl_PASTE( \
STATIC_ASSERT_failed_, \
line)[(2 * static_cast<int>(!!(condition))) - 1] _impl_UNUSED
#endif
#if defined(__GNUC__) || defined(__clang__) || defined(__xlc__)
#define ATOMIC_USE_GCC_INTRINSICS
#elif defined(_MSC_VER)
#define ATOMIC_USE_MSVC_INTRINSICS
#include "atomic_msvc.h"
#elif __cplusplus >= 201103L
#define ATOMIC_USE_CPP11_ATOMIC
#include <atomic>
#else
#error Unsupported compiler / system.
#endif
namespace atomic {
template <typename T>
class atomic {
public:
ATOMIC_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
sizeof(T) == 8,
"Only types of size 1, 2, 4 or 8 are supported");
atomic() : value_(static_cast<T>(0)) {}
explicit atomic(const T value) : value_(value) {}
/// @brief Performs an atomic increment operation (value + 1).
/// @returns The new value of the atomic object.
T operator++() {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::increment(&value_);
#else
return ++value_;
#endif
}
/// @brief Performs an atomic decrement operation (value - 1).
/// @returns The new value of the atomic object.
T operator--() {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::decrement(&value_);
#else
return --value_;
#endif
}
/// @brief Performs an atomic compare-and-swap (CAS) operation.
///
/// The value of the atomic object is only updated to the new value if the
/// old value of the atomic object matches @c expected_val.
///
/// @param expected_val The expected value of the atomic object.
/// @param new_val The new value to write to the atomic object.
/// @returns True if new_value was written to the atomic object.
bool compare_exchange(const T expected_val, const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
T e = expected_val;
return __atomic_compare_exchange_n(
&value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
const T old_val =
msvc::interlocked<T>::compare_exchange(&value_, new_val, expected_val);
return (old_val == expected_val);
#else
T e = expected_val;
return value_.compare_exchange_weak(e, new_val);
#endif
}
/// @brief Performs an atomic set operation.
///
/// The value of the atomic object is unconditionally updated to the new
/// value.
///
/// @param new_val The new value to write to the atomic object.
void store(const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
__atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
(void)msvc::interlocked<T>::exchange(&value_, new_val);
#else
value_.store(new_val);
#endif
}
/// @returns the current value of the atomic object.
/// @note Be careful about how this is used, since any operations on the
/// returned value are inherently non-atomic.
T load() const {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_load_n(&value_, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
// TODO(m): Is there a better solution for MSVC?
return value_;
#else
return value_;
#endif
}
/// @brief Performs an atomic exchange operation.
///
/// The value of the atomic object is unconditionally updated to the new
/// value, and the old value is returned.
///
/// @param new_val The new value to write to the atomic object.
/// @returns the old value.
T exchange(const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::exchange(&value_, new_val);
#else
return value_.exchange(new_val);
#endif
}
T operator=(const T new_value) {
store(new_value);
return new_value;
}
operator T() const {
return load();
}
private:
#if defined(ATOMIC_USE_GCC_INTRINSICS) || defined(ATOMIC_USE_MSVC_INTRINSICS)
volatile T value_;
#else
std::atomic<T> value_;
#endif
ATOMIC_DISALLOW_COPY(atomic)
};
} // namespace atomic
// Undef temporary defines.
#undef ATOMIC_USE_GCC_INTRINSICS
#undef ATOMIC_USE_MSVC_INTRINSICS
#undef ATOMIC_USE_CPP11_ATOMIC
#endif // ATOMIC_ATOMIC_H_

View File

@ -1,238 +0,0 @@
//-----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_ATOMIC_MSVC_H_
#define ATOMIC_ATOMIC_MSVC_H_
// Define which functions we need (don't include <intrin.h>).
extern "C" {
short _InterlockedIncrement16(short volatile*);
long _InterlockedIncrement(long volatile*);
__int64 _InterlockedIncrement64(__int64 volatile*);
short _InterlockedDecrement16(short volatile*);
long _InterlockedDecrement(long volatile*);
__int64 _InterlockedDecrement64(__int64 volatile*);
char _InterlockedExchange8(char volatile*, char);
short _InterlockedExchange16(short volatile*, short);
long __cdecl _InterlockedExchange(long volatile*, long);
__int64 _InterlockedExchange64(__int64 volatile*, __int64);
char _InterlockedCompareExchange8(char volatile*, char, char);
short _InterlockedCompareExchange16(short volatile*, short, short);
long __cdecl _InterlockedCompareExchange(long volatile*, long, long);
__int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64);
};
// Define which functions we want to use as inline intriniscs.
#pragma intrinsic(_InterlockedIncrement)
#pragma intrinsic(_InterlockedIncrement16)
#pragma intrinsic(_InterlockedDecrement)
#pragma intrinsic(_InterlockedDecrement16)
#pragma intrinsic(_InterlockedCompareExchange)
#pragma intrinsic(_InterlockedCompareExchange8)
#pragma intrinsic(_InterlockedCompareExchange16)
#pragma intrinsic(_InterlockedExchange)
#pragma intrinsic(_InterlockedExchange8)
#pragma intrinsic(_InterlockedExchange16)
#if defined(_M_X64)
#pragma intrinsic(_InterlockedIncrement64)
#pragma intrinsic(_InterlockedDecrement64)
#pragma intrinsic(_InterlockedCompareExchange64)
#pragma intrinsic(_InterlockedExchange64)
#endif // _M_X64
namespace atomic {
namespace msvc {
template <typename T, size_t N = sizeof(T)>
struct interlocked {
};
template <typename T>
struct interlocked<T, 1> {
static inline T increment(T volatile* x) {
// There's no _InterlockedIncrement8().
char old_val, new_val;
do {
old_val = static_cast<char>(*x);
new_val = old_val + static_cast<char>(1);
} while (_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
new_val,
old_val) != old_val);
return static_cast<T>(new_val);
}
static inline T decrement(T volatile* x) {
// There's no _InterlockedDecrement8().
char old_val, new_val;
do {
old_val = static_cast<char>(*x);
new_val = old_val - static_cast<char>(1);
} while (_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
new_val,
old_val) != old_val);
return static_cast<T>(new_val);
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
static_cast<const char>(new_val),
static_cast<const char>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(_InterlockedExchange8(
reinterpret_cast<volatile char*>(x), static_cast<const char>(new_val)));
}
};
template <typename T>
struct interlocked<T, 2> {
static inline T increment(T volatile* x) {
return static_cast<T>(
_InterlockedIncrement16(reinterpret_cast<volatile short*>(x)));
}
static inline T decrement(T volatile* x) {
return static_cast<T>(
_InterlockedDecrement16(reinterpret_cast<volatile short*>(x)));
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange16(reinterpret_cast<volatile short*>(x),
static_cast<const short>(new_val),
static_cast<const short>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(
_InterlockedExchange16(reinterpret_cast<volatile short*>(x),
static_cast<const short>(new_val)));
}
};
template <typename T>
struct interlocked<T, 4> {
static inline T increment(T volatile* x) {
return static_cast<T>(
_InterlockedIncrement(reinterpret_cast<volatile long*>(x)));
}
static inline T decrement(T volatile* x) {
return static_cast<T>(
_InterlockedDecrement(reinterpret_cast<volatile long*>(x)));
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange(reinterpret_cast<volatile long*>(x),
static_cast<const long>(new_val),
static_cast<const long>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(_InterlockedExchange(
reinterpret_cast<volatile long*>(x), static_cast<const long>(new_val)));
}
};
template <typename T>
struct interlocked<T, 8> {
static inline T increment(T volatile* x) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedIncrement64(reinterpret_cast<volatile __int64*>(x)));
#else
// There's no _InterlockedIncrement64() for 32-bit x86.
__int64 old_val, new_val;
do {
old_val = static_cast<__int64>(*x);
new_val = old_val + static_cast<__int64>(1);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(new_val);
#endif // _M_X64
}
static inline T decrement(T volatile* x) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedDecrement64(reinterpret_cast<volatile __int64*>(x)));
#else
// There's no _InterlockedDecrement64() for 32-bit x86.
__int64 old_val, new_val;
do {
old_val = static_cast<__int64>(*x);
new_val = old_val - static_cast<__int64>(1);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(new_val);
#endif // _M_X64
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x),
static_cast<const __int64>(new_val),
static_cast<const __int64>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedExchange64(reinterpret_cast<volatile __int64*>(x),
static_cast<const __int64>(new_val)));
#else
// There's no _InterlockedExchange64 for 32-bit x86.
__int64 old_val;
do {
old_val = static_cast<__int64>(*x);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(old_val);
#endif // _M_X64
}
};
} // namespace msvc
} // namespace atomic
#endif // ATOMIC_ATOMIC_MSVC_H_

View File

@ -1,78 +0,0 @@
//-----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_SPINLOCK_H_
#define ATOMIC_SPINLOCK_H_
#include "atomic.h"
namespace atomic {
class spinlock {
public:
spinlock() : value_(0) {}
/// @brief Acquire the lock (blocking).
/// @note Trying to acquire a lock that is already held by the calling thread
/// will dead-lock (block indefinitely).
void lock() {
while (!value_.compare_exchange(UNLOCKED, LOCKED))
;
}
/// @brief Release the lock.
/// @note It is an error to release a lock that has not been previously
/// acquired.
void unlock() { value_.store(UNLOCKED); }
private:
static const int UNLOCKED = 0;
static const int LOCKED = 1;
atomic<int> value_;
ATOMIC_DISALLOW_COPY(spinlock)
};
class lock_guard {
public:
/// @brief The constructor acquires the lock.
/// @param lock The spinlock that will be locked.
explicit lock_guard(spinlock& lock) : lock_(lock) {
lock_.lock();
}
/// @brief The destructor releases the lock.
~lock_guard() {
lock_.unlock();
}
private:
spinlock& lock_;
ATOMIC_DISALLOW_COPY(lock_guard)
};
} // namespace atomic
#endif // ATOMIC_SPINLOCK_H_

View File

@ -20,15 +20,17 @@
*/
#if !defined(HAVE_QT) || defined(HAVE_PTHREADS)
#include "tlThreads.h"
#include "tlUtils.h"
#include "tlTimer.h"
#include "tlSleep.h"
#include "tlLog.h"
#include "tlInternational.h"
#include <map>
#if defined(HAVE_PTHREADS)
#define _TIMESPEC_DEFINED // avoids errors with pthread-win and MSVC2017
#include <pthread.h>
#include <errno.h>
@ -39,12 +41,71 @@
# include <unistd.h>
#endif
#endif
namespace tl
{
// -------------------------------------------------------------------------------
// WaitCondition implementation
#if defined(HAVE_CPP20) || !defined(HAVE_QT) || defined(HAVE_PTHREADS)
#if defined(HAVE_CPP20)
class WaitConditionPrivate
{
public:
WaitConditionPrivate ()
: m_condition ()
{
}
~WaitConditionPrivate ()
{
}
bool wait (Mutex *mutex, unsigned long time)
{
bool result = true;
m_condition.clear (std::memory_order_release);
mutex->unlock ();
if (time != std::numeric_limits<unsigned long>::max ()) {
while (time > 0 && ! m_condition.test (std::memory_order_acquire)) {
tl::usleep (1000);
--time;
}
result = (time != 0);
} else {
m_condition.wait (false);
}
mutex->lock ();
return result;
}
void wake_all ()
{
if (! m_condition.test_and_set (std::memory_order_acquire)) {
m_condition.notify_all ();
}
}
void wake_one ()
{
if (! m_condition.test_and_set (std::memory_order_acquire)) {
m_condition.notify_one ();
}
}
private:
std::atomic_flag m_condition;
};
#else
class WaitConditionPrivate
{
@ -139,6 +200,8 @@ private:
bool m_initialized;
};
#endif
WaitCondition::WaitCondition ()
{
mp_data = new WaitConditionPrivate ();
@ -165,9 +228,13 @@ void WaitCondition::wakeOne ()
mp_data->wake_one ();
}
#endif
// -------------------------------------------------------------------------------
// Thread implementation
#if !(defined(HAVE_QT) && !defined(HAVE_PTHREADS))
class ThreadPrivateData
{
public:
@ -401,6 +468,6 @@ ThreadStorageHolderBase *ThreadStorageBase::holder ()
}
}
}
#endif
}

View File

@ -33,8 +33,7 @@
# include <QThread>
# include <QThreadStorage>
#else
// atomics taken from https://github.com/mbitsnbites/atomic
# include "atomic/spinlock.h"
# include <atomic>
#endif
namespace tl
@ -60,11 +59,21 @@ public:
class TL_PUBLIC Mutex
{
public:
Mutex () : m_spinlock () { }
void lock () { m_spinlock.lock (); }
void unlock () { m_spinlock.unlock (); }
Mutex () { }
void lock ()
{
while (flag.test_and_set (std::memory_order_acquire))
;
}
void unlock ()
{
flag.clear (std::memory_order_release);
}
private:
atomic::spinlock m_spinlock;
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
#endif
@ -75,7 +84,7 @@ private:
* available.
*/
#if defined(HAVE_QT) && !defined(HAVE_PTHREADS)
#if defined(HAVE_QT) && !defined(HAVE_PTHREADS) && !defined(HAVE_CPP20)
class TL_PUBLIC WaitCondition
: public QWaitCondition