C++ 2011:std::thread:¿ejemplo simple para paralelizar un ciclo?

C++ 2011:std::thread:¿ejemplo simple para paralelizar un ciclo?

std::thread no está necesariamente destinado a paralizar bucles. Está destinado a ser la abstracción de bajo nivel para construir construcciones como un algoritmo paralelo_para. Si desea paralizar sus bucles, debe escribir un algoritmo paralelo_para usted mismo o usar bibliotecas existentes que ofrecen paralelismo basado en tareas.

El siguiente ejemplo muestra cómo podría paralizar un ciclo simple pero, por otro lado, también muestra las desventajas, como la falta de equilibrio de carga y la complejidad de un ciclo simple.

  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

Usando una biblioteca que ofrece un parallel_for plantilla, se puede simplificar a

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

Bueno, obviamente, depende de lo que haga su ciclo, cómo elija paralelizar y cómo administre la vida útil de los subprocesos.

Estoy leyendo el libro de la biblioteca de subprocesamiento std C++11 (que también es uno de los mantenedores de boost.thread y escribió Just Thread) y puedo ver que "depende".

Ahora, para darle una idea de los conceptos básicos que utilizan el nuevo subprocesamiento estándar, recomendaría leer el libro, ya que ofrece muchos ejemplos. Además, eche un vistazo a http://www.justsoftwasolutions.co.uk/threading/ y https ://stackoverflow.com/questions/415994/boost-thread-tutorials


No puedo proporcionar una respuesta específica de C ++ 11 ya que todavía usamos principalmente pthreads. Pero, como respuesta independiente del lenguaje, paralelizas algo configurándolo para que se ejecute en una función separada (la función de subproceso).

En otras palabras, tienes una función como:

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

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

    exitThread()

y, en su código principal, puede procesar la matriz en dos partes:

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)

(teniendo en cuenta la advertencia de que debe asegurarse de que el subproceso 1 haya cargado los datos en su almacenamiento local antes el subproceso principal comienza a modificarlo para el subproceso 2, posiblemente con un mutex o usando una matriz de estructuras, una por hilo).

En otras palabras, rara vez se trata simplemente de modificar un for bucle para que se ejecute en paralelo, aunque eso sería bueno, algo como:

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

En su lugar, requiere un poco de reorganización de su código para aprovechar los subprocesos.

Y, por supuesto, debe asegurarse de que tenga sentido que los datos se procesen en paralelo. Si está configurando cada elemento de la matriz al anterior más 1, ninguna cantidad de procesamiento paralelo ayudará, simplemente porque tiene que esperar a que el elemento anterior se modifique primero.

Este ejemplo particular anterior simplemente usa un argumento pasado a la función de subproceso para especificar qué parte de la matriz debe procesar. La función de subproceso en sí contiene el ciclo para hacer el trabajo.