/*
  thread.c

  This is a simple demonsration program for using pthreads in C. It creates
  <n> threads and let them sleep a while (the work they have to do :-)
  Then it waits for the termination of all threads.

  The program is tested under Linux.

  Author: Ingo Kloeckl
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __linux__
#define _P __P
#endif

// two IPC sync objects to protect the variable iThreadCnt
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int iThreadCnt=0;  // count the completed threads

// the thread's working routine is defined here. Each thread gets a void* as
// argument and also return a void* as result or status code.
// You have to cast your own data to void*!
static void* do_work(void *arg){
  // extract input argument from void*
  int work = *(int*)arg;
  // pthread_self() returns the ID of the thread
  printf("thread %ld's work: %d\n", pthread_self(), work);
  sleep(work);
  printf("thread %ld fertig\n", pthread_self());

  // now increment the thread completion counter. The mainline can use this value
  // to check wether all started threads are completed.
  // To do so, we try to get a lock
  pthread_mutex_lock(&mutex);
  // do some work
  iThreadCnt++;
  // signal the mainline that something has happen
  pthread_cond_signal(&cond);
  // free the lock
  pthread_mutex_unlock(&mutex);

  // return the result or status code as void*
  return (void*)work;
}

int main(int argc, char**argv){
  int n=0, i;
  pthread_t tid[10];  // each thread has an ID of this type
  int arg[10];        // arguments for each thread
  int status[10];     // return codes for each thread

  if (argc!=2){
    printf("usage: %s <anz threads>\n", argv[0]);
    exit(1);
  }
  n = atoi(argv[1]);
  if (n>10){
    printf("n must be less than 11!\n");
    exit(1);
  }

  for (i=0; i<n; i++){
    // we define the work the thread has to do
    arg[i] = (10.*rand())/RAND_MAX;
    // we start the thread and pass NULL as attribute, so we use the standard
    // values in. The first parameter will be filled with the ID of the created
    // thread and passed back to main(). The last parameter is the argument to the
    // thread.
    pthread_create(&tid[i], NULL, &do_work, (void*)&arg[i]);
    printf("thread %d gestartet\n", tid[i]);
  }

  // now each thread is running and doing some work. We will wait here for
  // completion of each thread to catch its return value.
  // Instead of simply pthread_join() to all threads, we wait here until a counter
  // iThreadCnt reaches the number of started threads. This means, as many threads
  // are completed as were started. 
  printf("\nwaiting for threads to be finished ...\n");
  // try to get the lock protecting the variable
  pthread_mutex_lock(&mutex);
  while (iThreadCnt<n){
    // oh, not all threads are completed, so wait until we are awoken by a completing
    // thread. In this case, we check the counter again until the next awakening
    printf("waiting ...\n");
    pthread_cond_wait(&cond, &mutex);
  }
  // ok, all threads are terminated, free the lock and come to end ...
  pthread_mutex_unlock(&mutex);

  // the pthread_join() now returns immediately with the return status
  printf("perform join for each thread ...\n");
  for (i=0; i<n; i++){
    // wait for completion of the specified thread
    pthread_join(tid[i], (void*)&status[i]);
    printf("thread %d finished with result %d\n", tid[i], status[i]);
  }
}


