//Ein vollkommen sinnbefreites Beispiel zur pthread-
//Bibliothek

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

//es gibt zwei Threads vom typ Thread
#define THREADS 2

/*----------------------------------------------------------------------------*/

//Achtung: beim Kompilieren nicht die pthread-Bibliothek vergessen!
//gcc -lpthread -o threads threadexample.c

/*----------------------------------------------------------------------------*/

//1. Möglichkeit zur Kommunikation zwischen Threads: 
//Mutexe für den exklusiven Zugriff auf kritische Regionen,
//z.B. auf die globale Variable count
int count;
pthread_mutex_t count_mutex;

//Wir verwenden hier:
//pthread_mutex_init(pthread_mutex_t *mutex,
//                   const pthread_mutex_att_t *attr);
//pthread_mutex_destroy(pthread_mutex_t *mutex);
//pthread_mutex_lock(pthread_mutex_t *mutex);
//pthread_mutex_unlock(pthread_mutex_t *mutex);

//Außerdem gibt es noch:
//pthread_mutex_trylock(pthread_mutex_t *mutex);

//Dabei ist mutex immer ein Zeiger auf einen vorher angelegten
//pthread-Mutex
//attr sind Attribute für die Mutexe, die bezeichnen, ob
//ein Thread mehrfach ein Lock auf ein Mutex ausführen darf
//(Möglichkeiten sind Blockieren, Fehlerfall oder die Erlaubnis,
//dass auch zu tun)
//die Werte dafür sind nur unter Linux definiert und deshalb 
//nicht portabel auf andere (Randgruppen-)Betriebssysteme 


//2. Möglichkeit zur Kommunikation zwischen Threads:
//Warten, dass eine Bedingung wahr wird, d.h. wir warten
//auf Belegungen von Variablen
//Dazu brauchen wir:
//a) globale Variable, die für die Steuerung verwendet wird
int limit;
//b) für diese globale Variable eine pthread-condition-
//   Variable
pthread_cond_t limit_cond;
//c) Mutex für die Condition anlegen
//Dieser Mutex wird automatisch gesetzt!!!
pthread_mutex_t cond_mutex;

//Wir verwenden hier:
//pthread_cond_init(pthread_cond_t *cond,
//                  pthread_condattr_t *attr);
//pthread_cond_destroy(pthread_cond_t *cond);
//pthread_cond_wait(pthread_cond_t *cond);
//pthread_cond_signal(pthread_cond_t *cond);

//Außerdem gibt es noch
//pthread_cond_broadcast(pthread_cond_t *cond);
//pthread_cond_waittimed(pthread_cond_t *cond, 
//                       const struct timespec *time);

//Die Parameter funktionieren genauso wie bei den Mutexen.

/*----------------------------------------------------------------------------*/

//Ein Thread, der furchtbar spannende Dinge tut:
//Wir warten darauf, dass die globale Variable limit
//den Wert 15 erreicht. Wir freuen uns tierisch darüber
//und geben das auf dem Bildschirm aus.
//Dann pennen wir noch ein wenig und bringen uns selbst um.
void *Thread1(void *arg)
{
    //Argument auslesen
    int argument = *(int *) arg;
    int duration;
    int *retval;

    //Speicher freigeben
    free(arg);

    //Wir fangen an
    printf("%d macht was...\n", argument);

    //Warten auf die 15
    //Zugriff auf limit schützen

    pthread_mutex_lock(&cond_mutex);
    //auf signal warten, unlock/lock automatisch durch pthread_cond_wait
    //unlock auf mutex und suspendieren des Threads bis zum Signal ist
    //atomar!
    while(limit < 15)
	pthread_cond_wait(&limit_cond, &cond_mutex);
   
    //Juhuu!
    printf("%d erreicht - das ist toll!!!\n", limit);
 
    //Zugriff auf limit freigeben
    pthread_mutex_unlock(&cond_mutex);

    //Pennen
    duration = rand()%3;
    sleep(duration);

    //Und weg...
    printf("Und tschuess...\n");

    //Rückgabewert setzen
    retval = (int *)malloc(sizeof(int)); 
    *retval = argument;
    
    pthread_exit(retval);
}

/*----------------------------------------------------------------------------*/

//Noch ein toller Thread, gibt's aber zwei Instanzen von,
//mit den IDs 0 und 1
//Nummer 0 zählt die globale Variable count immer hoch,
//Nummer 1 immer runter
//Außerdem wird die globale Variable limit immer inkrementiert
//und bei Erreichen des Werts 15 unser lieber Thread 1
//darüber informiert.
//Der Zugriff auf die globlen Variablen ist natürlich mit 
//einem Mutex geschützt.
void *Thread(void *arg)
{
    //Parameter holen
    int argument = *(int *)arg;
    //Rückgabewert
    int *retval;
    //Zahl zum Warten, Zähler
    int duration, i;

    //Speicher für das Argument wieder freigeben
    free(arg);

    //Wir fangen an
    printf("%d: macht was...\n", argument);

    //10mal die Variable count hoch- bzw. runtersetzen
    //wenn die beiden Threads durch sind, ist count
    //wieder 0
    //10mal limit hochsetzen, am Ende ist limit also 20 
    for(i = 0; i < 10; i++)
    {
	//erstmal pennen, bevor die Arbeit anfängt
	duration = rand()%3;
	sleep(duration);

	//globale Variable anfordern
	pthread_mutex_lock(&count_mutex);
	
	//count in- bzw dekrementieren
	if(argument % 2 == 0)
	{
	    count++;
	}
	else
	    count--;
	
	printf("%d: count ist jetzt %d\n", argument, count);
	
	//kritischen Bereich wieder verlassen
	pthread_mutex_unlock(&count_mutex);

	//bei 15 müssen wir Bescheid sagen
	//Zugriff auf limit locken
	pthread_mutex_lock(&cond_mutex);

	//limit inkrementieren
	limit++;

	//Limit ausgeben
	printf("%d: limit ist jetzt %d\n", argument, limit);

	//wenn limit 15 ist, signalisieren
	if(limit == 15)
	    pthread_cond_signal(&limit_cond);

	//wieder freigeben
	pthread_mutex_unlock(&cond_mutex);
	
	//noch ne Runde auf's Ohr hauen
	sleep(duration);	
    }

    //Fertig
    printf("%d: Und weg...\n", argument);
    
    //Rückgabewert setzen
    //dafür müssen wir Speicher allokieren
    //einfach return geht nicht, denn jeder Thread
    //hat seinen eigenen Stack (wo ein return-Wert
    //normalerweise landet) und der ist für die 
    //anderen nicht verfügbar
    retval = (int *)malloc(sizeof(int)); 
    *retval = argument;

    //Thread beenden
    pthread_exit(retval);
}

int main(int argv, char** args)
{
    pthread_t handle[THREADS+1] ;
    int *argument;
    int *retval;
    int result, i;

    //Mutexe und Kondition initialisieren
    pthread_mutex_init(&count_mutex, NULL);
    pthread_mutex_init(&cond_mutex, NULL);
    pthread_cond_init(&limit_cond, NULL);

    //globale Variablen initialisieren
    count = 0;
    limit = 0;

    //Zufallszahlengenerator anschmeissen
    srand(time(NULL));

    //Threads anlegen

    //int pthread_create(pthread_t *handle), 
    //                   pthread_attr_t *att,
    //                   void *(*start_routine)(void *),
    //                   void *arg);
    //handle: Handle eines Thread
    //att: z.B. Scheduling-Policy, Stack-
    //                       groesse, ...
    //start_routine: die Threadfunktion
    //arg Wert des Parameters von start_routine
    //Rückgabewert: Id bei Thread, -1 bei Misserfolg

    for (i = 0; i < THREADS; i++)
    {
	
	//Argument immer neu anlegen, weil wir nicht wissen,
	//wann der Thread sich das abholt, sonst wird's 
	//evtl überschrieben
	//der Speicherplatz wird vom jeweiligen Thread
	//wieder freigegeben
	argument = (int *) malloc (sizeof(int));
	//Id setzen
	*argument = i;

	printf("%d faengt an...\n", *argument);
   
	//Thread erzeugen
	result = pthread_create(&(handle[i]), NULL, Thread, 
				argument);

	//hat nicht geklappt-schade auch
	if(result)
	{
	    printf("%d will nicht...\n", 
		   *argument);
	    exit(1);
	}
    }

    //Für den "Wir freuen uns über 15"-Thread müssen wir das
    //ganze nochmal machen
    argument = (int *) malloc(sizeof(int));
    *argument = i;

    result = pthread_create(&(handle[i]), NULL, Thread1, argument);
    printf("%d faengt an...\n", *argument);

    if(result)
    {
	printf("%d will nicht...\n", *argument);
	exit(1);
    }

    //void pthread_exit(void* retval) wird von den Threads
    //ausgeführt, um zu zeigen, dass sie fertig sind
    //mit retval können wir einen Returnwert zurückgeben

    //töten mit pthread_cancel(pthread_t handle)

    //wir warten nun darauf, dass alle fertig sind
    //und fügen alle parallelen Einheiten wieder zusammen
    for (i = 0; i < THREADS+1; i++)
    {
	//int pthread_join(pthread_t handle, void **retval)
	pthread_join(handle[i], (void **)&retval);

	printf("%d ist fertig...\n", *retval);

	//wir müssen noch den Speicher freigeben, den die
	//Threads für ihre Rückgabewerte angelegt haben
	free(retval);
    }

    //Mutex und Kondition wird freigeben
    pthread_mutex_destroy(&count_mutex);
    pthread_mutex_destroy(&cond_mutex);
    pthread_cond_destroy(&limit_cond);

    //Wenn wir nichts falsch gemacht haben, ist Limit nun 20
    //und count 0
    if((limit == 20) && (count == 0))
	printf("\n\nHurra!\n");
    else
	printf("\n\nMist!\n");
}

