#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h> 

/*
  Inicializacao estatica.

  NP significa Non-portable
  Para usar este inicializador, devemos compilar o programa
  informando a flag -D_GNU_SOURCE

*/
pthread_mutex_t lock = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;
pthread_barrier_t barrier;

                       
void* f_thread1(void *v) {
  int cont = 0;
  long i = 0;


  /*
    Mutexes do tipo Adaptivo aplicam-se somente a computadores
    multiprocessados.
    
    Neste tipo de mutex, uma thread quando não consegue obter o lock
    continua em "spin", executando um while onde a cada iteração
    tenta-se novamente a obtenção do lock.
    
    A cada chamada a pthread_mutex_lock, o número de vezes que uma thread
    itera é corrigido a fim de convergir para o número correto de iterações
    necessárias para obter o lock (daí vem o nome adaptivo). 
    
    Este número de "spins" é mantido na estrutura interna do mutex
    (campo __data.__spins).
    
    É importante ressaltar que caso a thread não obtenha o lock nos spins
    ela é colocada para dormir, assim como acontece com os mutexes
    normais.
     
    A idéia por trás deste mutex é evitar chamadas desnecessárias ao sleep,
    que faz com que a thread seja retirada do contexto de execução. Além
    disso, quando uma thread obtém o lock e é acordada, ela é novamente
    colocada no contexto. Operações de troca de contexto são caras; se a
    thread consegue o lock antes de ir dormir evitam-se estas operações.

    A desvantagem deste tipo de mutex é que a thread se mantem por um tempo
    em um estado de "busy-wait", gastando ciclos de CPUs desnecessariamente.
    


    Este programa tenta ilustrar estes conceitos fazendo com que as threads
    compitam pelo mutex por 50 vezes e imprimindo o número de spins
    determinado a cada iteração. A região crítica faz alguns cálculos para
    que o mutex seja mantido por uma das threads por um tempo mínimo e a
    outra tenha que aguardar.
    
    Em nossas máquinas, a cada execução deste programa o número de spins
    variou bastante, mostrando a capacidade de adaptação do mutex.
    
  */
  for (cont = 0; cont < 50; cont++) {

    pthread_mutex_lock(&lock);

    printf("Thread 1 - Lock obtido.\n");
    for (i = 0; i < 10000; i++) {
      int k = i*i*i;
    }
    printf("Thread 1 - Numero de spins: %d.\n", lock.__data.__spins);
    pthread_mutex_unlock(&lock);
    

    /*
      A barreira é utilizada para que na próxima iteração ambas
      threads concorram novamente pelo mutex.
     */
    pthread_barrier_wait(&barrier);
  }
  


  return NULL;
}


void* f_thread2(void *v) {
  int cont = 0;
  long i = 0;

  for (cont = 0; cont < 50; cont++) {

    pthread_mutex_lock(&lock);
    printf("Thread 2 - Lock obtido.\n");
    for (i = 0; i < 10000; i++) {
      int k = i*i*i; /* sqrt(i); */
    }
    printf("Thread 2 - Numero de spins: %d.\n", lock.__data.__spins);
    pthread_mutex_unlock(&lock);

    pthread_barrier_wait(&barrier);
  }


  return NULL;
}


int main() {
  pthread_t thr1, thr2;
  
  pthread_barrier_init(&barrier, NULL, 2);

  pthread_create(&thr1, NULL, f_thread1, NULL);
  pthread_create(&thr2, NULL, f_thread2, NULL);
  pthread_join(thr1, NULL);
  pthread_join(thr2, NULL);

  pthread_barrier_destroy(&barrier);
  
  printf("Finalizando programa.\n");

  return 0;
}