为什么没有atomic::try_load_for和try_store_for,类似于C++中的timed_mutex?

问题描述 投票:0回答:1

我有一个程序,我想保证在循环内取得进展,因为它需要响应安全关键事件。我的想法是为可以在该循环内进行的所有函数调用设置超时,但我需要原子数据类型。 std::atomic 在这里就足够了,但是没有 try_lock_for 函数,对吗?那么为什么 stdlib C++ 中还不存在类似下面的内容呢?

#ifndef TIMED_ATOMIC_HPP
#define TIMED_ATOMIC_HPP

#include <mutex>
#include <chrono>
#include <expected>
#include <memory>
#include <type_traits>

template<typename T>
struct is_shared_ptr : std::false_type {};

template <typename U>
struct is_shared_ptr<std::shared_ptr<U>> : std::true_type {};

template <typename T>
struct is_unique_ptr : std::false_type {};

template <typename U, typename D>
struct is_unique_ptr<std::unique_ptr<U, D>> : std::true_type {};

template <typename T>
struct is_raw_pointer : std::is_pointer<T> {};

template <typename T>
concept NotPointerType = !is_raw_pointer<T>::value && !is_unique_ptr<T>::value && !is_shared_ptr<T>::value;

template<typename T>
concept AllowedType = NotPointerType<T> && std::is_copy_constructible_v<T> && std::is_move_constructible_v<T>;

/// @brief Wrapper class to allow threadsafe access with a timeout. This is required because the robot state update thread should never hang because it is responsible for stopping the robot in case exit_flag is set.
template<typename T>
requires AllowedType<T>
class timed_atomic {
    private:
        std::timed_mutex mtx {};
        T value {};

    public:
        explicit timed_atomic(T initial_value) : value{initial_value} {}
        timed_atomic() = default;

        std::expected<T, std::monostate> try_load_for(std::chrono::microseconds timeout_us) {
            std::unique_lock lock{mtx, std::defer_lock};
            if(!lock.try_lock_for(timeout_us)) {
                return std::unexpected{std::monostate{}};
            }
            return value;
        }

        bool try_store_for(T new_value, std::chrono::microseconds timeout_us) {
            std::unique_lock lock{mtx, std::defer_lock};
            if(!lock.try_lock_for(timeout_us)) {
                return false;
            }
            value = std::move(new_value);
            return true;
        }
};

#endif

我尝试寻找 std::atomic::try_lock_for,类似于 timed_mutex 并找到了这个 C++20: How to wait on anatomic object with timeout?,但等待函数是我想说的其他东西。

c++ multithreading std stdatomic
1个回答
0
投票

std::atomic
的预期用例是在您关心的实现上无锁的类型。

某些系统上较大类型的回退到锁定只是为了实现可移植性。

对于无锁原子,没有尝试,只是做。 (这里插入“不要”笑话,我没想到一个好笑话。)

除非像

.fetch_or
这样的东西必须在x86上通过CAS重试循环来实现,如果您以不允许
lock bts
(位测试和设置)的方式使用返回值,则与
.fetch_add
不同有特别说明
lock xadd

最常见的情况确实有一个“尝试”,但是:

compare_exchange_weak
只是一次LL/SC尝试,即使值实际上匹配,也可能由于缓存行的争用而出现“虚假”失败。 需要重试循环来在仅具有加载链接/存储条件原子的机器上实现
compare_exchange_strong
,就像过去几年前的典型 RISC,其中 可扩展性到大量核心成为一个大问题。 (ARMv8.1 具有单指令原子 RMW。)

纯加载和纯存储永远不需要重试,除非使用 LL/SC 实现,类型太宽而无法使用纯加载/存储实现原子化。 例如 32 位 ARM 上的

int64_t
(Godbolt)

void store64(std::atomic<int64_t> &a, int64_t val){
    a.store(val, std::memory_order_relaxed);
}
store64(std::atomic<long long>&, long long):
        push    {r4, r5}
.L6:
        ldrexd  r4, r5, [r0]
        strexd  r1, r2, r3, [r0]
        cmp     r1, #0
        bne     .L6
        pop     {r4, r5}
        bx      lr

所以这里有一个重试循环。 但 ISO C++ 不会公开 LL/SC 或 CAS 重试,除了

compare_exchange_weak

它仍然是无锁的,至少如果硬件进行一些仲裁以防止 LL/SC 系统上的活锁(如果许多内核同时尝试),例如让一个内核在足够的周期内保留缓存行的所有权以完成 SC 部分只要交易不被中断即可。

© www.soinside.com 2019 - 2024. All rights reserved.