使用 std::atomic 实现互斥锁需要什么?

问题描述 投票:0回答:1
class AtomicLock
{
    std::atomic_flag flag;

public:

    void lock() {
        while (flag.test_and_set(std::memory_order_acquire))
            flag.wait(true, std::memory_order_acquire);
    }

    void unlock() {
        flag.clear(std::memory_order_release);
        flag.notify_one();
    }
};

class ThreadAtomicLock
{
    static std::atomic_long id_generator;
    std::atomic<long> owner_thread = -1;

    static long get_id() {
        thread_local auto id = id_generator++;
        return id;
    }

public:

    void lock() {
        long expected = -1;
        while (!owner_thread.compare_exchange_weak(expected, get_id(), std::memory_order_acquire)) {
            owner_thread.wait(expected, std::memory_order_acquire);
            expected = -1;
        }
    }

    void unlock() {
        long expected = get_id();
        assert (
            owner_thread.compare_exchange_strong(expected, -1, std::memory_order_release) &&
            "unlock of unowned lock"
        );
        owner_thread.notify_one();
    }
};

std::atomic_long ThreadAtomicLock::id_generator = 0;

void testAtomicLock() {
    std::vector<std::thread> threads;
    std::atomic<bool> flag = false;
    // AtomicLock lock;
    ThreadAtomicLock lock;
    for (int i = 0; i < 100; i++) {
        threads.emplace_back([&] {
            while (!flag.load(std::memory_order_acquire));
            lock.lock();
            std::cout << "Hello";
            std::cout << " from thread ";
            std::cout << std::this_thread::get_id() << " ";
            int n = 10;
            while (n--) std::cout << "=";
            std::cout << "\n";
            lock.unlock();
        });
    }
    flag.store(true, std::memory_order_release);
    for (auto& t : threads) t.join();
}

提供的

AtomicLock
类可以工作,但我意识到这将允许任何线程解锁锁,即使它不拥有它。

ThreadAtomicLock
是确保只有所有者线程可以解锁锁的尝试。

  1. 虽然它工作得很好,但我怀疑这就是实现锁所需的全部。这个实现正确吗?能不能放宽一些限制?

  2. 解锁功能中的

    compare_exchange_strong
    可以换成
    compare_exchange_weak
    吗?

  3. 这里的ID生成机制为每个线程存储一个额外的ID,并执行原子操作来为每个线程生成一个ID。虽然可以忽略不计,但我很好奇是否有更好的方法来实现它。

c++ multithreading concurrency locking stdatomic
1个回答
0
投票

(1) 我看起来是正确的。 C++20

.wait
.notify_one/all
公开了库互斥体实现利用的功能(如 Linux
futex
)来获得操作系统辅助的睡眠/唤醒。 某些实现在使用
.wait
进行系统调用之前会自旋重试几次。

您缺少的主要内容是避免在没有其他线程等待时在

unlock
中进行任何系统调用的方案。 在无竞争的情况下,这是在最佳情况下现代 x86 上可能需要 40 个周期的锁定/解锁之间的差异(无竞争和 L1d 缓存命中锁本身以及关键部分中访问的数据。例如,如果同一个线程是重复获取和释放相同的锁)到几百个时钟(涉及缓存未命中),而系统调用则需要几千个时钟(特别是现在需要进行 Spectre 缓解的任何系统调用)。

Glibc 的 pthread 互斥函数是开源的,因此您可以看一下它们如何让需要锁的线程在进入睡眠状态之前将其编码为锁整数。 https://codebrowser.dev/glibc/glibc/nptl/pthread_mutex_lock.c.html#211看起来像一个相关的评论,但我不知道是否有一个好的设计文档或指南来解释它。 (或其他实施的任何指南。)


(2)

compare_exchange_weak
可能会由于中断或错误共享而虚假失败。 您需要
compare_exchange_strong
,因为您没有在重试循环中使用它。

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