Попытка управления многопоточным доступом к массиву с помощью std :: atomic - PullRequest
0 голосов
/ 15 февраля 2020

Я пытаюсь контролировать многопоточный доступ к вектору данных , который имеет фиксированный размер , поэтому потоки будут ждать, пока их текущая позиция в нем не будет заполнена, прежде чем пытаться его использовать, или заполнят это сами, если еще никто. (Но убедитесь, что никто не ждет, если их позиция уже заполнена, или никто еще не сделал этого)

Однако я изо всех сил пытаюсь найти хороший способ сделать это, особенно с использованием std :: Atomi c. Я просто не очень знаком с концепциями многопоточности C ++, кроме использования базовых c std::thread.

Вот очень грубый пример проблемы:

class myClass
{
  struct Data
  {
    int res1;
  };

  std::vector<Data*> myData;

  int foo(unsigned long position)
  {
    if (!myData[position])
      {
        bar(myData[position]);
      }

    // Do something with the data
    return 5 * myData[position]->res1;
  }

  void bar(Data* &data)
  {
    data = new Data;

    // Do a whole bunch of calculations and so-on here

    data->res1 = 42;
  }
};

Теперь представьте, если foo() называется многопоточным, а несколько потоков может (или не может) иметь один и тот же position одновременно. Если это произойдет, существует вероятность того, что поток может (между созданием Data и завершением bar() попытаться фактически использовать данные.

Итак, каковы варианты ?

1: Создайте std :: mutex для каждой позиции в myData. Что, если в myData есть 10 000 элементов? Это 10 000 std :: mutexes, не очень. : Поместите вокруг него lock_guard следующим образом:

std::mutex myMutex;
{
    const std::lock_guard<std::mutex> lock(myMutex);

    if (!myData[position])
      {
        bar(myData[position]);
      }
}

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

3: использовать вектор символов и спин-блокировку как мьютекс бедного человека? Вот как это может выглядеть:

static std::vector<char> positionInProgress;
static std::vector<char> positionComplete;

class myClass
{
  struct Data
  {
    int res1;
  };

  std::vector<Data*> myData;

  int foo(unsigned long position)
  {   
    if (positionInProgress[position])
      {
        while (positionInProgress[position])
          {
            ;  // do nothing, just wait until it is done
          }
      }
    else
      {
        if (!positionComplete[position])
          {
            // Fill the data and prevent anyone from using it until it is complete
            positionInProgress[position] = true;
            bar(myData[position]);
            positionInProgress[position] = false;

            positionComplete[position] = true;
          }
      }

    // Do something with the data
    return 5 * myData[position]->res1;
  }

  void bar(Data* data)
  {
    data = new Data;

    // Do a whole bunch of calculations and so-on here

    data->res1 = 42;
  }
};

Этот , кажется, работает, но ни одна из операций тестирования или установки не является атомом c, поэтому у меня такое ощущение, что мне просто везет.

4: А как насчет std::atomic и std::atomic_flag? Ну, есть несколько проблем .

  • std::atomic_flag не имеет пути к test без установки в C ++ 11 ... что Это очень сложно.
  • std::atomic не является подвижным или копируемым, поэтому я не могу сделать из них вектор (я не знаю количество позиций во время построения myClass)

Заключение:

Это самый простой пример, который (вероятно) компилируется, который я могу придумать, который демонстрирует мою реальную проблему. На самом деле myData - это двумерный вектор, реализованный с использованием специального решения, созданного вручную, сами данные - это вектор указателей на более сложные типы данных, данные просто не возвращаются, и т. Д. c. Это лучшее, что я мог придумать.

1 Ответ

3 голосов
/ 15 февраля 2020

Самая большая проблема, с которой вы, вероятно, столкнетесь, заключается в том, что сам вектор не является потокобезопасным, поэтому вы не можете выполнить ЛЮБУЮ операцию, которая может изменить вектор (сделать недействительными ссылки на элементы вектора), в то время как другой поток может быть доступ к нему, например resize или push_back. Однако, если ваш вектор эффективно «фиксирован» (вы устанавливаете размер до появления потоков, а затем обращаетесь только к элементам, используя at или operator[], и никогда не изменяете сам вектор), вы можете избежать использования вектор атомов c объектов. В этом случае вы можете иметь:

std::vector<std::atomic<Data*>> myData;

и ваш код для настройки и использования элемента может выглядеть следующим образом:

if (!myData[position]) {
    Data *tmp = new Data;
    if (!mydata[position].compare_exchange_strong(nullptr, tmp)) {
        // some other thread did the setup
        delete tmp; } }
myData[position]->bar();

Конечно, вам все равно нужно убедиться, что операции выполнены члены Data в bar сами по себе являются многопоточными, так как здесь вы можете получить несколько потоков, вызывающих bar в одном и том же экземпляре Data.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...