Digamos que tengo el siguiente fragmento de código.

// Some function decleration
void generateOutput(const MyObj1& in, MyObj2& out);

void doTask(const std::vector<MyObj1>& input, std::vector<MyObj2>& output) {

    output.resize(input.size());

    // Use OpenMP to run in parallel

#pragma omp parallel for
    for (size_t i = 0; i < input.size(); ++i) {
        generateOutput(input[i], output[i]);
    }

}

¿Es seguro lo anterior? Me preocupa principalmente escribir a output[i]. ¿Necesito algún tipo de bloqueo? ¿O es innecesario? ex:


// Some function prototype
void generateOutput(const MyObj1& in, MyObj2& out);

void doTask(const std::vector<MyObj1>& input, std::vector<MyObj2>& output) {

    output.resize(input.size());

    // Use OpenMP to run in parallel

#pragma omp parallel for
    for (size_t i = 0; i < input.size(); ++i) {
        MyObj2 tmpOutput;
        generateOutput(input[i], tmpOutput);
#pragma omp critical
        output[i] = std::move(tmpOutput);
    }

}

No estoy preocupado por la parte de lectura. Como se menciona en esta respuesta, parece que leer input[i] es seguro.

1
cyrusbehr 28 abr. 2020 a las 01:38

3 respuestas

La mejor respuesta

output[i] no escribe en output. Esto es solo una llamada a std::vector<MyObj2>::operator[]. Devuelve un MyObj2& sin nombre, que luego se usa para llamar a generateOutput. Este último es donde ocurre la escritura.

Asumiré que generateOutput es seguro para subprocesos en sí, y MyObj2 también, ya que no tenemos código para eso. Por lo tanto, la escritura en MyObj2& dentro de generateOutput también es segura.

Como resultado, todas las piezas son seguras para roscas.

2
MSalters 28 abr. 2020 a las 11:27

Para no hacer ninguna suposición sobre la implementación de std :: vector, puede modificar su código de la siguiente manera para que sea seguro (las direcciones de puntero apuntarán por definición en diferentes zonas de la memoria y, por lo tanto, serán seguras para subprocesos)

// Some function decleration
void generateOutput(const MyObj1& in, MyObj2 *out); // use raw data pointer for output

void doTask(const std::vector<MyObj1>& input, std::vector<MyObj2>& output) {

    output.resize(input.size());

    // Use OpenMP to run in parallel


    auto data = output.data() ;// pointer on vector underlying data outside of OMP threading

    #pragma omp parallel for
    for (size_t i = 0; i < input.size(); ++i) {
        generateOutput(input[i], &data[i]); // access to distinct data elements  ie addresses (indexed by i only in from each omp thred)
    }

}
1
Jean-Marc Volle 30 abr. 2020 a las 09:54

Siempre y cuando esté garantizado que los subprocesos operan en elementos completamente separados (es decir, no se accede a ningún elemento por diferentes subprocesos diferentes sin algún tipo de sincronización), esto es seguro.

Dado que está utilizando un bucle for paralelo simple, en el que se accede a cada elemento exactamente una vez, esto es seguro.

1
mpoeter 28 abr. 2020 a las 09:55