/*
** Use multiple threads to find maximum value and its
** location in a large array
** Copyright Rolf Riesen 2008
**
*/
#include <stdio.h>
#include <stdlib.h>	/* For random(), srandom(), strtol(), exit(), malloc() */
#include <unistd.h>	/* For getopt() */
#include <string.h>	/* For memset() */
#include <errno.h>	/* For perror() */
#include <math.h>	/* For sqrt() */
#include <pthread.h>	/* For pthread_*() */


/* Constants */
#define DEFAULT_NUM_THREADS	(4)
#define DEFAULT_N		(10000000)
#define FALSE			(0)
#define TRUE			(1)


/* (Shared) Globals */
pthread_mutex_t global_lock= PTHREAD_MUTEX_INITIALIZER;
double global_max= 0.0;
int global_loc= -1;
int global_id= -1;



/* Local functions */
static double *alloc_array(int n);

static void *do_work(void *arg);
static void usage(char *pname);

typedef struct work_t   {
    int id;
    int start;
    int end;
    double *array;
} work_t;



int
main(int argc, char *argv[])
{

extern char *optarg;
int ch, error;

int nproc;
int n;
int i, rc;
double *array;
int region_size;
work_t *args;
pthread_t *thread;


 
    /* Initialize some variables */
    nproc= DEFAULT_NUM_THREADS;		/* Default number of threads */
    n= DEFAULT_N;                       /* Default matrix size */
    opterr= 0;	/* Don't let getopt print an error msg */
    error= FALSE;


    while ((ch= getopt(argc, argv, "p:n:")) != EOF)   {
	switch (ch)   {
	    case 'p':
		nproc= strtol(optarg, (char **)NULL, 0);
		if (nproc < 1)   {
		    error= TRUE;
		}
		break;
	    case 'n':
		n= strtol(optarg, (char **)NULL, 0);
		if (n < 1)   {
		    error= TRUE;
		}
		break;
	    default:
		error= TRUE;
	}
    }

    if (error)   {
	usage(argv[0]);
	exit(-1);
    }


    printf("Using %d threads to search %d entries.\n", nproc, n);

    /* Allocate storage for the thread handles */
    thread= (pthread_t *)malloc(nproc * sizeof(pthread_t));
    if (thread == NULL)   {
	fprintf(stderr, "Out of memory!\n");
	exit(-1);
    }

    /* Allocate storage for the thread arguments */
    args= (work_t *)malloc(nproc * sizeof(work_t));
    if (args == NULL)   {
	fprintf(stderr, "Out of memory!\n");
	exit(-1);
    }

    array= alloc_array(n);
    global_max= 0.0;
    global_loc= -1;
    region_size= (n / nproc) + 1;

    for (i= 0; i < nproc; i++)   {
	/* Set up the command struct for the next thread */
	args[i].id= i;
	args[i].array= array;
	args[i].start= i * region_size;
	args[i].end= ((i + 1) * region_size) - 1;
	if (args[i].end >= n)   {
	    args[i].end= n - 1;
	}

	rc= pthread_create(&thread[i], NULL, do_work,
		(void *)&args[i]);
	if (rc != 0)   {
	    perror("pthread_create() failed");
	    exit(-1); 
	}
    }

    for (i= 0; i < nproc; i++)   {
	pthread_join(thread[i], NULL);
    }


    printf("Done.              Max %17.15f at %d, found by thread %d\n",
        global_max, global_loc, global_id);

    return 0; 

}  /* end of main() */



static void *
do_work(void *arg)
{

work_t *args;
int i;
double my_max;
int my_loc;


    args= (work_t *)arg;
    my_max= 0.0;
    my_loc= -1;

    for (i= args->start; i <= args->end; i++)   {
	if (args->array[i] > my_max)   {
	    my_max= args->array[i];
	    my_loc= i;
	}
    }

    #ifdef DEBUG
        printf("[%3d] checked from %d to %d\n", args->id, args->start,
		args->end);
        printf("[%3d] found %17.15f at location %d\n", args->id,
		my_max, my_loc);
    #endif /*  DEBUG */

    pthread_mutex_lock(&global_lock);
    if (my_max > global_max)   {
        global_max= my_max;
        global_loc= my_loc;
        global_id= args->id;
    }
    pthread_mutex_unlock(&global_lock);

    return NULL;

}  /* end of do_work() */





/*
** Allocate memory for a size n array of doubles
*/
static double *
alloc_array(int n)
{

double *array;
int i;
double my_max;
int my_loc;


    array= (double *)malloc(sizeof(double) * n);
    if (array == NULL)   {
        fprintf(stderr, "Memory allocation failed!\n");
	exit(-1);
    }

    /* Fill it with random numbers */
    srandom(n);
    my_max= 0.0;
    my_loc= -1;
    for (i= 0; i < n; i++)   {
        array[i]= 1.0 / random();
	if (array[i] > my_max)   {
	    my_max= array[i];
	    my_loc= i;
	}
    }

    printf("Array initialized. Max %17.15f at location %d\n",
        my_max, my_loc);

    return array;

}  /* end of alloc_array() */



static void
usage(char *pname)
{

    fprintf(stderr, "Usage: %s [-p num] [-n num]\n", pname);
    fprintf(stderr, "           -p num   Create num threads\n");
    fprintf(stderr, "                    Default is %d.\n", DEFAULT_NUM_THREADS);
    fprintf(stderr, "           -n num   Size of array\n");
    fprintf(stderr, "                    Default is %d.\n", DEFAULT_N);

}  /* end of usage() */
