线程安全的无锁数组

8
我有一个C++库,它应该在多个线程上进行计算。我编写了独立的线程代码(即它们之间没有共享变量),除了一个数组。问题是,我不知道如何使其线程安全。
我查看了互斥锁/解锁(使用Qt的QMutex),但它不适用于我的任务-当一个线程锁定互斥锁时,其他线程将等待!
然后我了解了std::atomic,这看起来正是我所需要的。尽管如此,我尝试以以下方式使用它:
std::vector<std::atomic<uint64_t>> *myVector;

然后它产生了编译错误(使用已删除的函数'std::atomic :: atomic(const std::atomic&)' )。之后我发现了解决方案 - 使用特殊的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编译器。


你是想要一个固定大小(在多线程部分)的_shared elements_数组还是元素的_shared array_数组?也就是说,数组的插入和删除是否在代码的_multithreaded_部分进行? - Lol4t0
该数组具有固定的大小 - 我在线程中没有插入或删除操作。 - ahawkthomas
@ahawkthomas:你使用push_back时,通常会改变大小... - PlasmaHH
那么,如何在不使用push_back的情况下初始化std::vector?抱歉问这样愚蠢的问题,但我除了std::vector t = { 0, 0, 0 }之外什么都不知道——而且这种方法在大量项目时显然是无用的。 - ahawkthomas
2
还有,你明白吗,你的包装操作并不是原子操作。 - Lol4t0
3个回答

6

这是清理后的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。我还会考虑为 MobileAtomic 添加 storeload 方法,这将转发到 atomic.storeatomic.load

1
请告诉我如何访问那个向量?我想知道向量中存储了什么。 - Aditya kumar

2
你正在尝试复制一个不可复制的类型:AtomicUInt64 构造函数通过值获取 atomic
如果你需要从 atomic 初始化它,那么应该通过(const)引用获取参数。然而,在你的情况下,看起来你根本不需要从 atomic 初始化; 为什么不从 uint64_t 初始化呢?
还有一些小问题:
  • 拷贝构造函数和赋值操作符应该通过const引用获取它们的值,以允许临时变量被复制。

  • 使用new分配向量是一个相当奇怪的事情;你只是增加了一个额外的间接层,没有任何好处。

  • 确保您永远不要在其他线程可能访问它的情况下调整数组的大小。


非常感谢您对细节点的解释,特别是关于const引用的部分,我之前并不知道这些基础知识。 - ahawkthomas

1

这行

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

你完全忽略了传入的参数,你可能希望它是a.load(),并且你可能想通过const引用获取元素,以便它们不被复制。
AtomicUInt64 (const std::atomic<uint64_t>& a) : atomic (a.load()) {}

关于你所做的事情,我不确定是否正确。在数组内部修改变量将是原子的,但如果向量被修改或重新分配(这可能发生在push_back中),那么没有任何保证你的数组修改将在线程之间起作用并且是原子的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接