Подтвердить что ты не робот

С++ 2011: std:: thread: простой пример для параллелизации цикла?

С++ 2011 включает в себя очень интересные новые функции, но я не могу найти много примеров для параллелизации цикла for. Поэтому мой самый наивный вопрос: как вы распараллеливаете простой цикл цикла (например, используя "omp parallel for" ) с std:: thread? (Я ищу пример).

Большое спасибо.

4b9b3361

Ответ 1

std::thread не обязательно означает, что он будет перекрывать петли. Он предназначен для абстракции нижнего уровня для построения конструкций, подобных алгоритму parallel_for. Если вы хотите скомпонировать свои петли, вы должны либо самостоятельно использовать алгоритм parallel_for, либо использовать существующие libraires, которые предлагают основанный на задачах паралич.

В следующем примере показано, как вы можете парализовать простой цикл, но с другой стороны также показывает недостатки, такие как отсутствие балансировки нагрузки и сложность простого цикла.

  typedef std::vector<int> container;
  typedef container::iterator iter;

  container v(100, 1);

  auto worker = [] (iter begin, iter end) {
    for(auto it = begin; it != end; ++it) {
      *it *= 2;
    }
  };


  // serial
  worker(std::begin(v), std::end(v));

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 200

  // parallel
  std::vector<std::thread> threads(8);
  const int grainsize = v.size() / 8;

  auto work_iter = std::begin(v);
  for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) {
    *it = std::thread(worker, work_iter, work_iter + grainsize);
    work_iter += grainsize;
  }
  threads.back() = std::thread(worker, work_iter, std::end(v));

  for(auto&& i : threads) {
    i.join();
  }

  std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 400

Используя библиотеку, которая предлагает шаблон parallel_for, ее можно упростить до

parallel_for(std::begin(v), std::end(v), worker);

Ответ 2

Ну, очевидно, это зависит от того, что делает ваш цикл, как вы выбираете paralellize и как вы управляете жизненным циклом потоков.

Я читаю книгу из библиотеки потоков std С++ 11 (это также один из boost.thread и написал Just Thread), и я вижу, что " это зависит от.

Теперь, чтобы дать вам представление об основах, используя новую стандартную резьбу, я бы рекомендовал прочитать книгу, поскольку она дает множество примеров. Кроме того, посмотрите http://www.justsoftwaresolutions.co.uk/threading/ и https://stackoverflow.com/questions/415994/boost-thread-tutorials

Ответ 3

Невозможно предоставить конкретный ответ С++ 11, поскольку мы все еще в основном используем pthreads. Но, как язык-агностический ответ, вы что-то параллелируете, установив его для запуска в отдельной функции (функция потока).

Другими словами, у вас есть такая функция, как:

def processArraySegment (threadData):
    arrayAddr = threadData->arrayAddr
    startIdx  = threadData->startIdx
    endIdx    = threadData->endIdx

    for i = startIdx to endIdx:
        doSomethingWith (arrayAddr[i])

    exitThread()

и в вашем основном коде вы можете обработать массив двумя кусками:

int xyzzy[100]

threadData->arrayAddr = xyzzy
threadData->startIdx  = 0
threadData->endIdx    = 49
threadData->done      = false
tid1 = startThread (processArraySegment, threadData)

// caveat coder: see below.
threadData->arrayAddr = xyzzy
threadData->startIdx  = 50
threadData->endIdx    = 99
threadData->done      = false
tid2 = startThread (processArraySegment, threadData)

waitForThreadExit (tid1)
waitForThreadExit (tid2)

(имея в виду предостережение, которое должно гарантировать, что поток 1 загрузил данные в локальное хранилище до того, как основной поток начнет изменять его для потока 2, возможно, с помощью мьютекса или с помощью массива структур, по одному на поток).

Другими словами, редко бывает просто изменить цикл for так, чтобы он работал параллельно, хотя это было бы хорошо, что-то вроде:

for {threads=10} ({i} = 0; {i} < ARR_SZ; {i}++)
    array[{i}] = array[{i}] + 1;

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

И, конечно же, вы должны убедиться, что имеет смысл обрабатывать данные параллельно. Если вы устанавливаете каждый элемент массива на предыдущий плюс плюс 1, то никакая параллельная обработка не поможет, просто потому, что вам нужно дождаться изменения предыдущего элемента.

В этом примере выше просто используется аргумент, переданный функции потока, чтобы указать, какую часть массива он должен обрабатывать. Сама функция потока содержит цикл для выполнения работы.

Ответ 4

Используя этот класс, вы можете сделать это как:

Range based loop (read and write)
pforeach(auto &val, container) { 
  val = sin(val); 
};

Index based for-loop
auto new_container = container;
pfor(size_t i, 0, container.size()) { 
  new_container[i] = sin(container[i]); 
};

Ответ 5

AFAIK - самый простой способ распараллеливать цикл, если вы уверены, что нет возможности одновременного доступа, это использование OpenMP.

Он поддерживается всеми основными компиляторами, кроме LLVM (по состоянию на август 2013 года).

Пример:

for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

Это будет очень легко распараллеливаться следующим образом:

#pragma omp parallel for
for(int i = 0; i < n; ++i)
{
   tab[i] *= 2;
   tab2[i] /= 2;
   tab3[i] += tab[i] - tab2[i];
}

Однако помните, что это эффективно только при большом количестве значений.

Если вы используете g++, другой очень простой способ С++ 11-ish будет использовать lambda и for_each и использовать gnu-параллельные расширения (которые могут использовать OpenMP позади сцены):

__gnu_parallel::for_each(std::begin(tab), std::end(tab), [&] () 
{
    stuff_of_your_loop();
});

Однако for_each в основном рассматривается для массивов, векторов и т.д.... Но вы можете "обмануть" его, если хотите только итерации по диапазону, создав класс Range с begin и end, который будет в основном увеличивать int.

Обратите внимание, что для простых циклов, которые выполняют математический материал, алгоритмы в #include <numeric> и #include <algorithm> могут быть распараллелены с помощью g++.

Ответ 6

Определить макрос, используя выражение std:: thread и lambda:

#ifndef PARALLEL_FOR
#define PARALLEL_FOR(INT_LOOP_BEGIN_INCLUSIVE, INT_LOOP_END_EXCLUSIVE,I,O)          \                                                               \
    {                                                                               \
        int LOOP_LIMIT=INT_LOOP_END_EXCLUSIVE-INT_LOOP_BEGIN_INCLUSIVE;             \
        std::thread threads[LOOP_LIMIT]; auto fParallelLoop=[&](int I){ O; };       \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i]=std::thread(fParallelLoop,i+INT_LOOP_BEGIN_INCLUSIVE);       \
        }                                                                           \
        for(int i=0; i<LOOP_LIMIT; i++)                                             \
        {                                                                           \
            threads[i].join();                                                      \
        }                                                                           \
    }                                                                               \
#endif

использование:

int aaa=0;
PARALLEL_FOR(0,90,i,
{
    aaa+=i;
});

его уродливый, но он работает.