Sincronização entre Threads


Informação complementar:

Esta aula tem por objetivo introduzir a sincronização entre threads através da utilização de semáforos e locks. Para tal, deverá implementar pequenos programas que respondam aos requisitos de cada uma das tarefas que se seguem. Para obter mais informação sobre as funções de sistema referidas, consulte as man-pages respetivas.

  1. Sincronização por semáforos: o programa (thread principal) deve criar duas threads (Thr1 e Thr2), as quais devem imprimir alternadamente linhas, de modo a obter uma sequência semelhante à seguinte:
        Thr1: 1
        Thr2: 2
        Thr1: 3
        ...
        Thr2: 18
        Thr1: 19
        Thr2: 20
    A thread Thr1 deve imprimir as linhas ímpares e a thread Thr2 as linhas pares, até atingirem um determinado valor (20 no exemplo). Cada thread deverá ter associado um semáforo (neste caso será mais conveniente utilizar semáforos unnamed). Para indicar à outra thread a sua vez de imprimir, a thread deverá sinalizar o semáforo associado a essa thread. Deverá depois aguardar (no seu semáforo) que a outra thread lhe passe novamente a vez de imprimir.
    Funções a ter em conta: pthread_create(), pthread_join(), pthread_exit(), sem_init(), sem_wait(), sem_post() e sem_destroy().
  2. Exclusão mútua entre threads: o programa que se segue implementa uma versão sequencial do cálculo do produto interno entre dois vetores. Para executar o programa deverá invocar o seguinte comando:
    ./a.out vector_size threads
    em que vector_size é o tamanho dos vetores e threads é o número de threads envolvidas na computação (este argumento será utilizado mais tarde, no código abaixo é simplesmente ignorado).

     
      #include <stdio.h>
      #include <stdlib.h>
    
      long long *create_vector1(int);
      long long *create_vector2(int);
      void do_work(void);
      void produto_interno(int, int);
    
      int threads, vector_size;
      long long prod_int, *vector_a, *vector_b;
    
      int main(int argc, char *argv[]) {
        vector_size = atoi(argv[1]);
        threads = atoi(argv[2]);
        vector_a = create_vector1(vector_size);
        vector_b = create_vector2(vector_size);
        do_work();
        return EXIT_SUCCESS;
      }
    
      long long *create_vector1(int size) {
        int i;
        long long *vector;
    
        vector = (long long *) malloc(size * sizeof(long long));
        for (i = 0; i < size; i++)
          vector[i] = i;	
        return vector;
      }
    
      long long *create_vector2(int size) {
        int i;
        long long *vector;
    
       vector = (long long *) malloc(size * sizeof(long long));
        for (i = 0; i < size; i++)
          vector[i] = size-i-1;	
        return vector;
      }
    
      void do_work(void) {
        prod_int = 0;
        produto_interno(0, vector_size);
        printf("Produto Interno = %lld\n", prod_int);
        return;
      }
    
      void produto_interno(int start, int end) {
        int i;
    
        for (i = start; i < end; i++) 
          prod_int += vector_a[i] * vector_b[i];
        return;
      }
    

    1. Teste o programa para tamanhos dos vetores elevados (por exemplo 10000), registando o valor obtido para o produto interno.
    2. Modifique o programa anterior de modo a dividir o cálculo do produto interno por um conjunto de threads igual ao valor indicado pelo argumento threads. Por exemplo, se indicarmos T threads e se os vetores tiverem tamanho N, então cada thread poderá calcular N/T componentes do produto interno e ir somando o resultado na variável global prod_int. Para tal deverá fazer o lançamento das threads, calcular as componentes do produto interno que cada deverá calcular e sincronizar a atualização da variável prod_int entre todas as threads. No final, compare os valores obtidos para o produto interno com os da alínea anterior.
      Funções a ter em conta: pthread_create(), pthread_join(), pthread_exit(), pthread_mutex_init(), pthread_mutex_lock() e pthread_mutex_unlock().