C++

Introducció

En aquest exemple, explorarem com enviar a Multivac un programa usant el mòdul omp per a paral·lelitzar i compilar un programa que obre diferents threads.

Crearem un arxiu per exemple anomenat test_openmp.c amb finalitats demostratives.

Codi d’exemple

#include <omp.h>
#include <stdio.h>
#include <unistd.h> // Per a la funció sleep()

int main() {
    // Exemple 1: Threads s'executen sense ordre específic
    printf("Exemple 1: Threads sense ordre específic\n");

    // Variable compartida
    int shared_data = 0;

    #pragma omp parallel
    {
        int id = omp_get_thread_num();

        // Cada thread modifica la variable compartida sense sincronització
        ++shared_data;
        printf("Thread %d ha modificat shared_data a %d\n", id, shared_data);


        // Simula una tasca
        sleep(1);

        // Cada thread llegeix la variable compartida
        printf("Thread %d llegeix shared_data: %d\n", id, shared_data);
    }

    // Exemple 2: Threads es passen informació entre ells i s'esperen
    printf("\nExemple 2: Threads amb comunicació i sincronització\n");

    // Reinicialitzem la variable compartida
    shared_data = 0;

    #pragma omp parallel shared(shared_data)
    {
        int id = omp_get_thread_num();
        int n = omp_get_num_threads();

        for (int i = 0; i < n; ++i) {
            #pragma omp barrier // Espera que tots els threads arribin aquí

            if (id == i) {
                // Thread actual modifica la variable compartida
                ++shared_data;
                printf("Thread %d ha actualitzat shared_data a %d\n", id, shared_data);
                sleep(1);
        }

            #pragma omp barrier // Espera que la modificació sigui visible per a tots els threads

            // Tots els threads esperen en ordre per llegir la variable compartida
            for (int j = 0; j < n; ++j) {
                #pragma omp barrier
                if (id == j) {
                    printf("Thread %d llegeix shared_data: %d\n", id, shared_data);
                }
            }
        }
    }

    return 0;
}

Execució

Crearem un script amb extensió .slurm anomenat test_omp.slurm que li diu a Multivac com ha d’executar el codi.

VERSION=1.3
JOB_NAME=test
NAME_OUTPUT=out
PARTITION=all
N_TASKS=4
CPUS_PER_TASK=2
MAIL_TYPE=END,FAIL
MAIL_USER=alexandre.gracia@upc.edu
MEMORY=15000M                # [K|M|G|T] log-c1: 3715M, log-c2:3779M, log-c3:3715M, log-c4:3779M, dops-a1:15879M, dops-a2:15879M, dops-a3:15878M, dops-a4:15900M, dops-a5:15900M, cetus:64166M, psi:257379M
BEGIN=now                   # "hh:mm","now+1hour", "now+60" (seconds by default), "2010-01-20T12:34:00" YYYY-MM-DD[THH:MM[:SS]]
TIME_LIMIT=23:59:00         # "minutes", "minutes:seconds", "hours:minutes:seconds", "days-hours", "days-hours:minutes", "days-hours:minutes:seconds"
LOG_OUTPUT=log
FORCED_NODES=               # Nodes en els què es forçarà l'execució
EXCLUDED_NODES=             # Nodes que seràn exclosos de l'execució
ROUTE=~/tests/test_omp      # Ruta dels nostres scripts
COMMANDS=(
    # Comandes d'informació
    "hostname"
    "whoami"

    # Compilar el programa
    "g++ -fopenmp test_openmp.c -o test_openmp"

    # Ejecutar el programa
    "./test_openmp"

)

Aquest script el llançarem des de iocex usant la següent comanda:

multivac test_omp.slurm

Output

La sortida del nostre script hauria de ser similar a aquesta. En el primer Exemple, els threads s’executen sense ordre concret. En el segon Exemple, els threads llegeixen una variable compartida i n’extreuen el seu contingut amb un ordre concret, esperant-se a l’script anterior.

dops-a3
alexandre.gracia
Exemple 1: Threads sense ordre específic
Thread 4 ha modificat shared_data a 1
Thread 1 ha modificat shared_data a 5
Thread 6 ha modificat shared_data a 7
Thread 0 ha modificat shared_data a 2
Thread 3 ha modificat shared_data a 6
Thread 5 ha modificat shared_data a 3
Thread 2 ha modificat shared_data a 4
Thread 7 ha modificat shared_data a 5
Thread 7 llegeix shared_data: 7
Thread 2 llegeix shared_data: 7
Thread 5 llegeix shared_data: 7
Thread 0 llegeix shared_data: 7
Thread 1 llegeix shared_data: 7
Thread 6 llegeix shared_data: 7
Thread 4 llegeix shared_data: 7
Thread 3 llegeix shared_data: 7

Exemple 2: Threads amb comunicació i sincronització
Thread 0 ha actualitzat shared_data a 1
Thread 0 llegeix shared_data: 1
Thread 1 llegeix shared_data: 1
Thread 2 llegeix shared_data: 1
Thread 3 llegeix shared_data: 1
Thread 4 llegeix shared_data: 1
Thread 5 llegeix shared_data: 1
Thread 6 llegeix shared_data: 1
Thread 7 llegeix shared_data: 1
Thread 1 ha actualitzat shared_data a 2
Thread 0 llegeix shared_data: 2
Thread 1 llegeix shared_data: 2
Thread 2 llegeix shared_data: 2
Thread 3 llegeix shared_data: 2
Thread 4 llegeix shared_data: 2
Thread 5 llegeix shared_data: 2
Thread 6 llegeix shared_data: 2
Thread 7 llegeix shared_data: 2
Thread 2 ha actualitzat shared_data a 3
Thread 0 llegeix shared_data: 3
Thread 1 llegeix shared_data: 3
Thread 2 llegeix shared_data: 3
Thread 3 llegeix shared_data: 3
Thread 4 llegeix shared_data: 3
Thread 5 llegeix shared_data: 3
Thread 6 llegeix shared_data: 3
Thread 7 llegeix shared_data: 3
Thread 3 ha actualitzat shared_data a 4
Thread 0 llegeix shared_data: 4
Thread 1 llegeix shared_data: 4
Thread 2 llegeix shared_data: 4
Thread 3 llegeix shared_data: 4
Thread 4 llegeix shared_data: 4
Thread 5 llegeix shared_data: 4
Thread 6 llegeix shared_data: 4
Thread 7 llegeix shared_data: 4
Thread 4 ha actualitzat shared_data a 5
Thread 0 llegeix shared_data: 5
Thread 1 llegeix shared_data: 5
Thread 2 llegeix shared_data: 5
Thread 3 llegeix shared_data: 5
Thread 4 llegeix shared_data: 5
Thread 5 llegeix shared_data: 5
Thread 6 llegeix shared_data: 5
Thread 7 llegeix shared_data: 5
Thread 5 ha actualitzat shared_data a 6
Thread 0 llegeix shared_data: 6
Thread 1 llegeix shared_data: 6
Thread 2 llegeix shared_data: 6
Thread 3 llegeix shared_data: 6
Thread 4 llegeix shared_data: 6
Thread 5 llegeix shared_data: 6
Thread 6 llegeix shared_data: 6
Thread 7 llegeix shared_data: 6
Thread 6 ha actualitzat shared_data a 7
Thread 0 llegeix shared_data: 7
Thread 1 llegeix shared_data: 7
Thread 2 llegeix shared_data: 7
Thread 3 llegeix shared_data: 7
Thread 4 llegeix shared_data: 7
Thread 5 llegeix shared_data: 7
Thread 6 llegeix shared_data: 7
Thread 7 llegeix shared_data: 7
Thread 7 ha actualitzat shared_data a 8
Thread 0 llegeix shared_data: 8
Thread 1 llegeix shared_data: 8
Thread 2 llegeix shared_data: 8
Thread 3 llegeix shared_data: 8
Thread 4 llegeix shared_data: 8
Thread 5 llegeix shared_data: 8
Thread 6 llegeix shared_data: 8
Thread 7 llegeix shared_data: 8

El resultat del nostre programa un cop finalitzada l’execució serà visible al mateix directori a on hem fet l’execució.

Com podem observar en el primer cas, com que els threads no s’esperen, l’ordre és aleatori, i cadascun dels threads llegeix la variable respecte el seu ordre però no assegurant el valor de la variable respecte el que correspon, acabant un resultat inesperat.