【问题标题】:std::atomic on struct bit-fields结构位域上的 std::atomic
【发布时间】:2019-06-04 09:18:26
【问题描述】:

我正在修改一些现有的开源库,并且有一个包含位字段的结构(例如命名为 Node),例如

struct Node {
    std::atomic<uint32_t> size:30;
    std::atomic<uint32_t> isnull:1;
};

为了满足我的需要,这些字段需要是原子的,所以我希望为此使用 std::atomic 并面临编译时错误:

bit-field 'size' has non-integral type 'std::atomic<uint32_t>'

根据文档,有一组受限制的类型可用于 std::atomic

任何人都可以建议/知道如何在对现有源代码影响最小的情况下获得原子字段的功能吗?

提前致谢!

【问题讨论】:

  • 没有原子位字段这样的东西。典型的 CPU 根本不提供机器指令来原子地操作位字段。所以 C++ 语言不允许这样的结构,因为它是无法实现的。
  • @IgorTandetnik 在哪个现实世界的 CPU 上无法实现?
  • @curiousguy 在 x86 上,据我所知。至少,不是没有锁或自旋循环。

标签: c++11 concurrency atomic bit-fields


【解决方案1】:

我在下面使用了一个无符号的 short 作为示例。

这不太理想,但您可以牺牲 8 位并在带有联合的位字段中插入 std::atomic_flag。不幸的是,std::atomic_flag 类型是 std::atomic_bool 类型。

每次访问该结构时都可以手动自旋锁定。但是,代码应该具有最小的性能下降(与使用 std::mutexstd::unique_lock 创建、锁定、解锁、销毁不同)。

此代码可能会浪费大约 10-30 个时钟周期来启用低成本多线程。

PS。确保下面保留的 8 位不会被处理器的字节序结构弄乱。您可能必须在最后定义大端处理器。我只在 Intel CPU(总是 little-endian)上测试了这段代码。

#include <iostream>
#include <atomic>
#include <thread>

union Data
{
    std::atomic_flag access = ATOMIC_FLAG_INIT; // one byte
    struct
    {
        typedef unsigned short ushort;

        ushort reserved : 8;
        ushort count : 4;
        ushort ready : 1;
        ushort unused : 3;
    } bits;
};


class SpinLock
{
public:
    inline SpinLock(std::atomic_flag &access, bool locked=true)
        : mAccess(access)
    {
        if(locked) lock();
    }

    inline ~SpinLock()
    {
        unlock();
    }

    inline void lock()
    {
        while (mAccess.test_and_set(std::memory_order_acquire))
        {
        }
    }

    // each attempt will take about 10-30 clock cycles
    inline bool try_lock(unsigned int attempts=0)
    {
        while(mAccess.test_and_set(std::memory_order_acquire))
        {
            if (! attempts) return false;
            -- attempts;
        }

        return true;
    }

    inline void unlock()
    {
        mAccess.clear(std::memory_order_release);
    }

private:
    std::atomic_flag &mAccess;
};

void aFn(int &i, Data &d)
{
    SpinLock lock(d.access, false);
    
    // manually locking/unlocking can be tighter
    lock.lock();
    if (d.bits.ready)
    {
        ++d.bits.count;
    }
    d.bits.ready ^= true; // alternate each time
    lock.unlock();
}

int main(void)
{
    Data f;
    f.bits.count = 0;
    f.bits.ready = true;

    std::thread *p[8];
    for (int i = 0; i < 8; ++ i)
    {
        p[i] = new std::thread([&f] (int i) { aFn(i, f); }, i);
    }

    for (int i = 0; i < 8; ++i)
    {
        p[i]->join();
        delete p[i];
    }

    std::cout << "size: " << sizeof(f) << std::endl;
    std::cout << "count: " << f.bits.count << std::endl;
}

结果如预期......

尺寸:2
计数:4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-17
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多