/* Pthreads example. Multiple processes counting.
 * Compile this program with
 * 	gcc -o pt_example pt_example.c
 * Run this program with one argument. The number of threads to create.
 * 	./pt_example 5
 * Run it several times to see what happens.
 * */

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

/* See "man pthreads" for lots of info.
 See also:
  	pthread_self()
  	pthread_equal()
 
 	pthread_mutex_trylock()
 
 Condition variable routines:
 	pthread_cond_init()
 	pthread_cond_wait()
 	pthread_cond_destroy()
*/

pthread_mutex_t global_mutex;
int global_target = 250000;
int global_counter = 0;

typedef struct
{
    pthread_t tid;
    int thr_num;
    int val;
    int ret;
} thread_registry_t;



/* This is the routine that is a thread-safe global counter incrementer */
int
increment(void)
{
    int num=0;
    pthread_mutex_lock(&global_mutex);
    /* here is my CRITICAL SECTION */
    if (global_counter < global_target)
    {
	global_counter++;
	num=1;
    }
    pthread_mutex_unlock(&global_mutex);
    return num;
}


/* Just a little routine to chew up some time */
int
work(int n)
{
    int l=0;
    while(n)
    {
	n = n / 2;
	l++;
    }
    return l;
}

/* Routine called on the birth of threads as created by run_threads() */
void *
thread_start_routine(void *arg)
{
    int cnt=0 ,ret, inp_val;
    thread_registry_t *tid_entry = (thread_registry_t*)arg;

    inp_val = tid_entry->val;
    printf("input value for thread = %d\n", inp_val);

    while(ret = increment())
    {
	work(cnt);
	cnt++;
    }

    tid_entry->ret = cnt;
    return arg;
}

/* Create num threads and wait for them all using join call. */
void
run_threads(int num)
{
    int i, status;
    thread_registry_t *tid_array;
    pthread_attr_t attr;

    /* setup mutex */
    status = pthread_mutex_init(&global_mutex, NULL);
    if (status != 0)
    {
	fprintf(stderr, "Mutex init error: %s\n", strerror(status));
	return;
    }

    /* create num threads */
    tid_array = (thread_registry_t*)malloc(num * sizeof(thread_registry_t));

    /* Initialize pthread attribute object */
    pthread_attr_init(&attr);
    /* set attribute so that threads are created as system scheduled threads*/
    /* Try commenting out this line and see what happens!!!!!!! */
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

    for(i=0; i< num; ++i)
    {
	tid_array[i].thr_num = i;
	tid_array[i].val = i;
	tid_array[i].ret = 0;
	status = pthread_create(&tid_array[i].tid, &attr,
	    thread_start_routine, &tid_array[i]);
	if (status != 0)
	{
	    fprintf(stderr, "Creation error on thread %d: %s\n",
		i, strerror(status));
	}

    }

    printf("main thread: All threads created. Waiting for death\n");

    for(i=0; i< num; ++i)
    {
	thread_registry_t *tid_entry;
	void *retptr;
	int retval;

	status = pthread_join(tid_array[i].tid, &retptr);
	if (status != 0)
	{
	    fprintf(stderr, "Join error on thread %d: %s\n",
		i, strerror(status));
	}
	else
	{
	    tid_entry = (thread_registry_t*)retptr;
	    printf("Thread %d terminated with value %d representing the number of increments it got in\n",
		tid_entry->thr_num, tid_entry->ret);
	}
    }

    /* Clean up memory */
    free(tid_array);

    /* clean up mutex */
    status = pthread_mutex_destroy(&global_mutex);
    if (status != 0)
    {
	fprintf(stderr, "Mutex destroy error: %s\n", strerror(status));
    }

}


/* Simple main program */
int
main(int argc, char *argv[])
{
    int num;


    if (argc != 2)
    {
	fprintf(stderr, "use: pt_example num_threads\n");
	exit(1);
    }

    num = atoi(argv[1]);
    if (num < 1)
    {
	fprintf(stderr, "pt_example: invalid number of threads\n");
	exit(1);
    }

    run_threads(num);

    return 0;
}

