【问题标题】:Thread-safe lock-free array线程安全的无锁数组
【发布时间】:2013-05-30 23:14:44
【问题描述】:

我有一个 C++ 库,它应该在多个线程上进行一些计算。我制作了独立的线程代码(即它们之间没有共享变量),除了一个数组。问题是,我不知道如何使它成为线程安全的。

我查看了互斥锁/解锁(QMutex,因为我正在使用 Qt),但它不适合我的任务 - 虽然一个线程会锁定互斥锁,但其他线程会等待!

然后我读到了std::atomic,它看起来正是我所需要的。尽管如此,我还是尝试通过以下方式使用它:

std::vector<std::atomic<uint64_t>> *myVector;

并且它产生了编译器错误(使用已删除的函数'std::atomic::atomic(const std::atomic&)')。然后我找到了the solution - 为std::atomic 使用特殊的包装器。我试过这个:

struct AtomicUInt64
{
    std::atomic<uint64_t> atomic;

    AtomicUInt64() : atomic() {}

    AtomicUInt64 ( std::atomic<uint64_t> a ) : atomic ( atomic.load() ) {}

    AtomicUInt64 ( AtomicUInt64 &auint64 ) : atomic ( auint64.atomic.load() ) {}

    AtomicUInt64 &operator= ( AtomicUInt64 &auint64 )
    {
                atomic.store ( auint64.atomic.load() );
    }
};

std::vector<AtomicUInt64> *myVector;

这个东西编译成功了,但是当我无法填充向量时:

myVector = new std::vector<AtomicUInt64>();

for ( int x = 0; x < 100; ++x )
{
    /* This approach produces compiler error:
     * use of deleted function 'std::atomic<long long unsigned int>::atomic(const std::atomic<long long unsigned int>&)'
     */
    AtomicUInt64 value( std::atomic<uint64_t>( 0 ) ) ;
    myVector->push_back ( value );

    /* And this one produces the same error: */
    std::atomic<uint64_t> value1 ( 0 );
    myVector->push_back ( value1 );
}

我做错了什么?我想我尝试了一切(也许不是,无论如何),但没有任何帮助。在 C++ 中还有其他方法可以实现线程安全的数组共享吗?

顺便说一句,我在 Windows 上使用 MinGW 32bit 4.7 编译器。

【问题讨论】:

  • 您想要一个固定大小(在多线程部分)的共享元素数组还是共享元素数组。我的意思是,数组中的插入和删除是否存在于代码的多线程部分中?
  • 数组的大小是固定的——我没有在线程中插入或删除。
  • @ahawkthomas:你 push_back,这通常会改变大小......
  • 那么,如何在不使用push_back 的情况下初始化std::vector?对于这些愚蠢的问题,我很抱歉,但除了std::vector t = { 0, 0, 0 },我什么都不知道——而且这种方法显然对大量项目没有用处。
  • 另外,你明白吗,你的包装操作不是原子操作

标签: c++ arrays thread-safety


【解决方案1】:

您正在尝试复制不可复制的类型:AtomicUInt64 构造函数采用 atomic 的值。

如果您需要它可以从atomic 初始化,那么它应该通过 (const) 引用获取参数。但是,在您的情况下,您似乎根本不需要从 atomic 初始化;为什么不从uint64_t 初始化呢?

还有一些小问题:

  • 复制构造函数和赋值运算符应通过const 引用获取它们的值,以允许复制临时对象。

  • new 分配向量是一件相当奇怪的事情;您只是添加了额外的间接级别,没有任何好处。

  • 确保在其他线程可能正在访问数组时,切勿调整数组大小。

【讨论】:

  • 非常感谢您的小点解释,特别是const参考,我不知道这些基础知识。
【解决方案2】:

这是您的AtomicUInt64 类型的清理版本:

template<typename T>
struct MobileAtomic
{
  std::atomic<T> atomic;

  MobileAtomic() : atomic(T()) {}

  explicit MobileAtomic ( T const& v ) : atomic ( v ) {}
  explicit MobileAtomic ( std::atomic<T> const& a ) : atomic ( a.load() ) {}

  MobileAtomic ( MobileAtomic const&other ) : atomic( other.atomic.load() ) {}

  MobileAtomic& operator=( MobileAtomic const &other )
  {
    atomic.store( other.atomic.load() );
    return *this;
  }
};

typedef MobileAtomic<uint64_t> AtomicUInt64;

并使用:

AtomicUInt64 value;
myVector->push_back ( value );

或:

AtomicUInt64 value(x);
myVector->push_back ( value );

您的问题是您按值获取了std::atomic,这会导致复制被阻止。哦,你没能从operator=回来。我还明确了一些构造函数,可能是不必要的。我将const 添加到您的复制构造函数中。

我也很想将storeload 方法添加到MobileAtomic 转发到atomic.storeatomic.load

【讨论】:

  • 请告诉我如何访问该向量?我想知道向量中存储了什么。
【解决方案3】:

这一行

AtomicUInt64 ( std::atomic<uint64_t> a ) : atomic ( atomic.load() ) {}

您完全忽略了您传入的参数,您可能希望它是 a.load() 并且您可能希望通过 const 引用获取元素,这样它们就不会被复制。

AtomicUInt64 (const std::atomic<uint64_t>& a) : atomic (a.load()) {}

至于你在做什么,我不确定它是否正确。数组内变量的修改将是原子的,但如果向量被修改或重新分配(push_back 可能),则无法保证您的数组修改将在线程之间工作并且是原子的。

【讨论】:

    猜你喜欢
    • 2010-12-14
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多