Насколько безопасно использование индекса std :: atomic <int>для асинхронного распараллеливания операции с большим массивом - PullRequest
0 голосов
/ 27 ноября 2018

У меня есть распараллеленная подпрограмма для выполнения серии вычислений для каждого объекта в большом массиве указателей, где вычисления требуют, чтобы каждый поток мог прочитать все другие объекты, но когда-либо будет записывать только один объект.Я настроил его так, как показано ниже

#include <atomic>
#include <thread>

void threadFunction(Object** objects, int n);

std::atomic<int> idx;
int nobjects = 10000;

int main() {
  int nthreads = 4;
  Object** objects = new Object*[nobjects];

  idx = 0;
  std::thread threads[nthreads];
  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii] = std::thread(threadFunction, objects, ii);
  }

  while (idx < nobjects - 1) {}    // Wait until all the calculations have been done

  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii].join();
  }
}

void threadFunction(Object** objects, int n) {
  Object* current = NULL;
  while (idx < nobjects - 1) {
    current = objects[idx++];
    // do calculation
  }
}

Где Object - это пользовательский класс, но для этих целей его можно заменить на примитив.У меня вопрос, как «безопасно» делает это таким образом?Я понимаю, что atomic типы защищены от частичной записи, но насколько я могу быть уверен, что это будет работать каждый раз, даже для больших массивов?

Это может быть слишком конкретным, но я был бы признателен за любую помощь, которую я могу получить.Спасибо

1 Ответ

0 голосов
/ 27 ноября 2018

Как отмечали другие в комментариях, у вас есть условие гонки между проверкой состояния вашего цикла и использованием значения idx.Это может заставить вас читать за концом массива.Для вашей функции потока требуется небольшая настройка:

void threadFunction(Object** objects, int n) {
  Object* current = NULL;
  while (true) {
    int next = idx++;
    if (next < nobjects - 1) {
      current = objects[next];
      // do calculation
    } else {
      break;
    }
  }
}

В общем, доказать, что алгоритм без блокировок верен, сложно, и это можно сделать только путем тщательной проверки кода.Размер массива никоим образом не влияет на корректность этого алгоритма.

Использование стандартной библиотеки

Хотя об этом специально не спрашивали, вероятно, стоит указатьчто все это можно сделать через стандартную библиотеку (которая позволяет избежать сложных вопросов безопасности и обрабатывает такие вопросы, как разбиение).Примерно так:

void DoCalculations(Object& obj)
{
  // details...
}

// later...

std::vector<std::unique_ptr<Object>> objects = CreateObjects();
std::for_each(
  std::execution::par,
  objects.begin(),
  objects.end(),
  [] (std::unique_ptr<Object> const& p) { DoCalculations(*p); });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...