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

// Die Anzahl an Threads in diesem Beispielprogramm.
// (Sollte immer gerade sein.)
#define NUM_THREADS 2

// Eine globale Variable und ein Mutex, dass den konkurrierenden Zugriff 
// auf diese Variable verhindern soll.

int             global = 0;
pthread_mutex_t global_mutex;

// Der Thread
// Als Übergabewert wird ein void-Pointer angenommen. Hinter diesem
// Pointer kann sich ein Basis-Datentyp aber auch ein struct verbergen.
// Der Rückgabewert ist ebenfalls wieder ein void-Pointer.

void *Thread(void *arg) {
    int threadid = *(int*)arg;
    int *retval;
    int duration, temp;

    // Gib den reservierten Speicher für den Übergabewert wieder frei.
    free(arg);

    printf("%d: starting thread.\n", threadid);

    for (int i = 0; i < 5; i++) {
	duration = rand() % 500000;
	usleep(duration);

	// Stelle sicher, dass sich nur ein Prozess zur Zeit im
	// kritischen Abschnitt befindet.
	pthread_mutex_lock(&global_mutex);
	printf("%d: enter\n", threadid);
	
	// Manipuliere die globale Veriable.
 	if (threadid % 2) temp = global + 1;
	else temp = global - 1;
	usleep(duration);
	global = temp;

	// Beende den kritischen Abschnitt.
	printf("%d: leave\n", threadid);
	pthread_mutex_unlock(&global_mutex);

	usleep(duration);
    }
    
    printf("%d: terminating thread\n", threadid);

    // Reserviere den Speicher für den Rückgabewert.
    retval = (int*)malloc(sizeof(int));
    *retval = threadid;

    // Beende den Thread.
    pthread_exit(retval);
}

main() {
    pthread_t th[NUM_THREADS];
    int *argument, *joinresult;
    int result, i;
 
    // Initialisiere den Mutex.
    pthread_mutex_init(&global_mutex, NULL);

    // Starte NUM_THREADS threads, die die globale Variable verändern.
    for (i = 0; i<NUM_THREADS; i++) {
	argument = (int*) malloc(sizeof(int));
	*argument = i;
	printf("Starting thread %d with argument %d\n", i, *argument);

	// Starte den Thread
	// th[i] enthält hinterher den thread identifier
	// Thread ist die Funktion, die als Thread ausgeführt werden soll.
	// argument ist ein pointer zu den Funktionsargumenten.
	result = pthread_create(&(th[i]), NULL, Thread, argument);

	if (result) {
	    printf("Creation of thread %d failed.\n", i);
	    exit(1);
	}
    }

    // Warte auf das Terminieren aller Threads.
    for (i = 0; i<NUM_THREADS; i++) {
	pthread_join(th[i], &((void*)joinresult));
	printf("Terminated thread %d returned %d.\n", i, *joinresult);
	free(joinresult);
    }
    
    // Fehlermeldung, wenn die globale Variable ungleich Null ist.
    if (global != 0)
	printf("Error: global = %d\n", global);
}

