提问者:小点点

如何线程安全函数添加元素到数组?


我编写了以下函数,它接受一个对象(element),在现有向量(elements)中为其腾出空间,并通过添加新对象来更新向量:

void addElement(const ElementType& element) {
    if (numElements == elements.size()) {
        elements.resize(boost::extents[numElements+1]);
    }

    elements[numElements] = element;

    numElements++;
}

我如何使它对MPI是线程安全的? 据我所知,每个线程都知道elements的大小,因此我不明白为什么这个函数不是线程安全的。 NumElements在此函数之外初始化为零,是Elements向量的大小。

编辑:我使用了上面写的函数,mtx锁定和解锁如下所示,但是最后的elements向量仍然只包含第一级的数据。

    #pragma omp parallel for collapse(3) schedule(static)
    for (long n0 = mgr->startN0; n0 < mgr->startN0 + mgr->localN0; n0++) {
      for (int n1 = 0; n1 < Ν; n1++) {
        for (int n2 = 0; n2 < Ν; n2++) {
          ElementType element;
          std::mutex mtx;
            for(int i=0;i<g_field[n0][n1][n2];i++){
              ... do stuff with element ...
              mtx.lock();
              #pragma omp critical
              addElement(element);
              mtx.unlock();}
       }
     }
   }

共1个答案

匿名用户

首先,让我们看看数据竞赛何时发生:

  • 两个或多个线程同时访问同一内存位置
  • 至少有一个访问用于写入
  • 线程没有使用任何独占锁来控制它们对该内存的访问

在您的示例中,一个线程可以添加元素,而另一个线程正在读取elements。 而且,多个线程可以同时添加新元素。 要控制它们的访问,您应该锁定共享资源,即elements

如何在C++中锁定共享资源?

在C++中,可以使用std::mutex保护共享数据不被多个线程同时访问。 当std::mutex被一个线程锁定时,其他线程无法访问共享资源。 一旦std::mutex被解锁,其他线程就可以访问该资源。 您可以使用std::mutex::lockstd::mutex::unlock,例如:

std::mutex mtx;
ElementsType elements;

...
mtx.lock();

// only one thread accesses this part at a time
// work with elements

mtx.unlock();

开发人员经常会忘记一些东西。。。

由于开发人员经常忘记一些东西。。。 例如解锁被锁定的std::mutex。。。 C++提供了std::lock_guard(在C++11的情况下)和std::scoped_lock(在C++17的情况下)。 您可以将std::lock_guardstd::scoped_lock视为某种包装器,它们将std::mutex实例作为构造函数的参数,并将该std::mutex实例锁定在构造函数中。 当std::lock_guardstd::scoped_lock实例被销毁时,std::mutex实例将自动解锁。

void addElement(const ElementType& element) {
    std::lock_guard<std::mutex> lock(mtx); // similar to mtx.lock()

    if (numElements == elements.size()) {
        elements.resize(boost::extents[numElements+1]);
    }

    elements[numElements] = element;

    numElements++;

    // no need for mtx.unlock() since lock instance is now destructed and mutex is automatically unlocked
}

编辑

由于std::mutex是所有线程之间的某种通信通道,所以所有线程应该共享相同的std::mutex实例,即相同的std::mutex实例应该对所有线程可见。

在以下情况下(我摘自更新的问题):

for (int n2 = 0; n2 < Ν; n2++) {
    ElementType element;
    std::mutex mtx;
    for(int i=0;i<g_field[n0][n1][n2];i++){
         // ... do stuff with element ...
         mtx.lock();
         #pragma omp critical
         addElement(element);
         mtx.unlock();}
    }
}

std::mutex被创建了N次,每个线程都创建了自己的std::mutex实例,这没有任何意义,因为它们不能相互通信。 相反,一个std::mutex实例应该对所有线程都可见,就像elements对所有线程都可见一样。