/* 05Jun24: Beispiel zur Verwendung von POSIX Threads (Fäden) in C,
 * anhand von Motiv Matrix-Vektor-Multiplikation. */

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

#include "sem.h"

#define N 100

/* Das ist hier nur ein Makro um leicht ein Array der Größe N
 * auszugeben, mit dem Namen der Variable. */
#define DUMP(arr)				\
     do {					\
	  printf("%s:", #arr);			\
	  for (int i = 0; i < N; i++)		\
	       printf(" %g", arr[i]);		\
	  puts("");				\
     } while (0)

/* a: Die Eingabe Matrix,
 * b: Der Eingabe Vektor,
 * c: Der Ausgabe Vektor (a × c)  */
static double a[N][N], b[N], c[N];

/* Eine Semaphore, welche wir in diesem Programm dazu nutzen um
 * gegenseitigen Ausschluss umzusetzen.  Diese wird Anfangs auf 1
 * initialisiert, und verwaltet damit wie viele Fäden zu einem
 * Zeitpunkt auf gemeinsamen Speicher arbeiten dürfen. */
static SEM *lock;

static void init()
{
     /* Wir initialisieren a auf eine Einheits-Matrix */
     for (int i = 0; i < N; i++) {
	  for (int j = 0; j < N; j++) {
	       a[i][j] = i == j ? 1 : 0;
	  }
	  b[i] = i+1;
     }

     /* Und benutzen die Funktion aus der sem.h Schnittstelle, um eine
      * Semaphore anzulegen. */
     lock = semCreate(1);	/* TODO: Fehlerbehandlung */
}

/* Implementierung der Matrix-Vektor Multiplikation, für eine Reihe
 * ROW, wo die Summe aus dieser Reihe in der Variable TSUM aufadiert
 * wird. */
static void mvmult(int row, double *tsum)
{
     double sum = 0;
     
     for (int col = 0; col < N; col++) {
	  sum += a[row][col] * b[row];
     }

     c[row] = sum;

     P(lock);
     *tsum += sum;
     V(lock);
}

/* Wir definieren einen eigenen Datentypen, um dem Übergabeparameter
 * für einen neuen Thread mit der main-Funktion `multiplier' mehr
 * Struktur zu geben. */
struct mjob {
     int row;
     double *sum;
};

/* Und hier ist die Funktion, welche aufgerufen wird von einem neuen
 * Thread.  Es benutzt wiederhin den gleichen Speicher, muss aber den
 * void*-Zeiger erst uminterpretieren.  Die eigentliche Arbeit wird in
 * diesem Fall, nachdem wir das Argument richtig interpretiert haben
 * an `mvmult' weitergegeben. */
void *multiplier(void *arg)
{
     struct mjob *mjob = arg;

     mvmult(mjob->row, mjob->sum);

     return NULL;
}

int main()
{
     init();			/* initialise a, b and c */

     DUMP(b);

     /* Der Datentyp `pthread_t' ist vergleichbar zu `pid_t' für
      * Unix-Prozesse.  Wir haben damit eine Weise, wir wir der
      * Pthread-Bibliothek mitgeben können, um uns bei Operationen auf
      * spezifische Fäden zu beziehen (in diesem Fall wird es sein,
      * dass wir darauf warten wollen, bis ein Thread terminiert). */
     pthread_t tids[N];

     double sum = 0;
     for (int i = 0; i < N; i++) {
	  /* Argumente für die Threads brauchen alle ihren eigenen
	   * Speicher, weil wir uns nicht darauf verlassen können in
	   * welcher Reihenfolge diese Ausgeführt werden. */
	  struct mjob *mjob = malloc(sizeof(struct mjob));
	  if (mjob == NULL) {
	       perror("malloc");
	       exit(EXIT_FAILURE);
	  }
	  *mjob = (struct mjob) { .row = i, .sum = &sum }; 

	  /* Hier werden die Threads erstellt.  Wir speichern uns hier
	   * alle TIDs ab, weil wir die später noch benutzen müssen
	   * (ansonsten könnte man die innerhalb von der Schleife
	   * anlegen und verwerfen), und müssen ansonsten nur die
	   * Main-Funktion für den Thread wie auch das Argument
	   * angeben: */
	  errno = pthread_create(&tids[i], NULL, multiplier, mjob);
	  if (errno != 0) {
	       perror("pthread_create");
	       exit(EXIT_FAILURE);
	  }
     }

     /* Nachdem die Threads erstellt wurde, warten wir in dem
      * Haupt-Thread nur noch darauf, dass sich alle beenden, damit
      * der Prozess nicht frühzeitig beendet wird.  Das machen
      * dadurch, dass wir nacheinander darauf warten, bis sich alle
      * threads beenden.  Wenn sich für n > m, Thread-n vor Thread-m
      * beendet, ist das kein Problem, weil `pthread_join' dann gleich
      * zurückkehrt.  */
     for (int i = 0; i < N; i++) {
	  pthread_join(tids[i], NULL);
     }
     
     DUMP(c);

     /* Das Ergebnis der Summe hier wird nicht stimmen, wenn wir oben
      * die Semaphore nicht benutzt hätten.  Man kann damit Spielen,
      * wie bei mehr Threads und ohne die Semaphore das Ergebnis
      * variiert.  Hier kann es auch nutzlich sein -fsanitize=thread
      * beim Übersetzen anzugeben, damit zur Laufzeit
      * Wettlaufsitiationen erkannt und gemeldet werden. */
     printf("total sum: %g\n", sum);

     semDestroy(lock);
     
     return 0;
}