/* 07Jun24: Beispiel für POSIX Threads um Pi zu approximieren. */ #include #include #include #include #include #include #include "sem.h" #define N 1000 /* anzahl an threads */ /* siehe https://en.wikipedia.org/wiki/Monte_Carlo_method, hier weiter * nicht weiter inhaltlich interessant über die tatsache hinaus, dass * es "embarrassingly parallelizable" * (https://en.wikipedia.org/wiki/Embarrassingly_parallel) Problem * ist. */ static unsigned monte_carlo(unsigned iters) { unsigned hits; while (iters --> 0) { double x = ((double) rand()) / RAND_MAX; double y = ((double) rand()) / RAND_MAX; if (x * x + y * y < 1) { hits++; } } return hits; } /* Threads nehmen anstatt "char **argv" ein Zeiger zu beliebigen Daten * im gleichen Speicher-Raum. Es eignet sich oft zu diesem Zweck ein * eigenen struct zu definieren, wo wir strukturiert Parameter + * gemeinsamen Speicher übergeben können: */ struct job { /* Wir übergeben die Anzahl der Experimente und Speicher damit * das Ergebnis protokolliert werden kann, */ unsigned runs, hits; /* So wie eine Semaphore um gegenseitigen Ausschluss zu regeln, * wenn man auf `hits' arbeiten will. */ SEM *lock; }; /* Die main-Funktion vom Thread: Das Argument muss um-interpretiert * werden in einen spezifischen Typen =struct job=. */ void *counter(void *arg) { struct job *job = arg; unsigned n = monte_carlo(job->runs); /* Hier ein Beispiel für einen kritischen Abschnitt, wo * neben-läufiger Zugriff synchronisiert wird mittels einer * Binären Semaphore. Ohne diesen Fall, konnten wir mit eine * Wettlauf-Situation erkennen mit der GCC Option * -fsanitize=thread. */ P(job->lock); job->hits += n; V(job->lock); return NULL; } int main(int argc, char *argv[]) { SEM *lock = semCreate(1); /* FEHLERBEHANDUNG! */ /* Weiter unwichtig, wir müssen nur den PRNG * (Zufallszahlen-Generator) initialisieren mit einem * Systemabhängigem-Wert, damit es nicht immer die gleichen * Ergebnisse liefert. */ srand((unsigned) time(NULL)); if (argc < 2) { fprintf(stderr, "usage: %s [iters]\n", argv[0]); return EXIT_FAILURE; } struct job job = { .runs = atoi(argv[1]), /* `atoi' (ascii to integer) ist * KEINE robuste funktion, und sollte * NICHT in SP benutzt werden! */ .lock = lock, }; /* Wir brauchen N viele Prozess Handles, weil wir alle Threads * wieder joinen wollen. Ansonsten wäre es auch möglich gewesen * in der Schleife ein tid anzulegen und zu verwerfen. */ pthread_t tid[N]; for (int i = 0; i < N; i++) { errno = pthread_create(&tid[i], NULL, counter, &job); if (errno != 0) { perror("pthread_create"); exit(EXIT_FAILURE); } } /* In dem wir versuchen auf jeden Thread zu warten, bildet die * Schleife ein Sychrnonisations-Punkt für den weiteren Verlauf * des Programms. */ for (int i = 0; i < N; i++) { pthread_join(tid[i], NULL); } printf("approximation of pi (%u/%u): %g\n", job.hits, job.runs * N, 4 * ((double) job.hits) / (job.runs * N)); semDestroy(lock); /* Semaphoren müssen auch aufgeräumt werden! */ return EXIT_SUCCESS; } /* Postscript: Das Beispiel, wie Threads das Programm langsamer werden * lassen kann, wird in diesem Artikel gegeben: * https://brooker.co.za/blog/2014/12/06/random.html. */