summaryrefslogtreecommitdiffstats
path: root/04_exercise/threadpool.h
blob: 5ffefdf5efcfe6b7a1bff254118a1eddb9d95ad2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#ifndef THREADPOOL_H_INCLUDED
#define THREADPOOL_H_INCLUDED

#include <stdatomic.h>
#include <stddef.h>
#include "ppmlib/ppmlib.h"

/**@brief Funktionszeiger auf eine asynchron auszuführende Funktion.
 *
 * Der Parameter kann zur Übergabe von Argumenten und den Rückgabewerten
 * der Funktion genutzt werden.
 */
typedef void (*ThreadTask_f)(void *);

typedef atomic_char FutureStatus;
enum FurutureStatusEnum {
    FUT_WAITING = 0, FUT_IN_PROGRESS = 1, FUT_DONE = 2
};
/**@brief Handle zum zukünftigen Rückgabewert eines asynchronen Funktionsrufes.
 */
typedef struct Future {
    ThreadTask_f fn;   ///<@brief Zeiger auf die auszuführende Funktion.
    /* TODO: benötigte Attribute hinzufügen */
    FutureStatus status;
}                   Future;

/**@brief Initialisiert den globalen Thread-Pool.
 * 
 * Diese Funktion legt \p size viele Worker-Threads an und initialisiert den
 * restlichen Zustand des Thread-Pools. Danach werden keine weiteren Threads
 * mehr durch den Thread-Pool angelegt.
 * 
 * @param size  Anzahl der Worker-Threads
 */
extern int tpInit(size_t size);

/**@brief Beendet alle Worker-Threads im Thread-Pool kontrolliert und gibt
 * sämtliche Systemressourcen des Thread-Pools frei.
 * 
 * Bereits gestartete Futures können abgebrochen oder fertiggestellt werden.
 */
extern void tpRelease(void);

/**@brief Startet eine nutzerdefinierte Future.
 * 
 * Die Funktion initialisiert die übergebene Future und fügt sie zur Liste
 * der abzuarbeitenden Tasks hinzu. Die Abarbeitungsreihenfolge ist
 * unspezifiziert und die Abarbeitung startet sofort (eager).
 * 
 * @param future  partiell-initialisiertes Future-Objekt
 * @note Der Rufer ist dafür verantwortlich, dass der Speicher nicht vor
 * der Auswertung des asynchronen Funktionsrufes freigegeben wird.
 */
extern void tpAsync(Future *future);

/**@brief Erwartet die Abarbeitung einer Future.
 * 
 * Nachdem diese Funktion zurückgekommen ist, ist der asynchrone Funktionsruf
 * garantiert abgeschlossen und der Zugriff auf die Future Data-Race-frei.
 * Die Funktion gibt auch alle Systemressourcen des Thread-Pools frei.
 * 
 * @param future  asynchron gestartetes Future-Objekt
 * @note Der Rufer kann danach den Speicher für die Future wieder freigeben.
 */
extern void tpAwait(Future *future);

/**@brief Erzeugt Code für eine spezialisierte Schnittstelle einer Funktion.
 * 
 * Die direkte Nutzung der minimalistischen Schnittstelle des Thread-Pools ist
 * unkomfortabel und fehleranfällig, daher ist hier ein Makro definiert,
 * mithilfe dessen eine spezifische Schnittstelle für asynchrone Funktionen
 * erzeugt werden kann.
 * 
 * Das Makro muss im Filescope expandiert werden und erzeugt folgende Elemente:
 *   1. eine spezifische Datenstruktur für Argumente und Rückgabewerte der
 *      konkreten Funktion mit dem Namen `func_fut`
 *   2. eine sogenannte thunk-Funktion mit dem Namen `funcThunk`, welche die
 *      Funktionsargumente aus dem generischen Argument entpackt, die konkrete
 *      Funktion ausführt und dann den Rückgabewert zurückschreibt
 *   3. eine asynchrone Ruf-Funktion mit dem Namen `funcAsync`, welche ein
 *      Future-Objekt initialisiert und zurückgibt
 *   4. eine synchrone Warte-Funktion mit dem Namen `funcAwait`, welche auf die
 *      Abarbeitung eines zuvor asynchron gerufenen Future-Objektes wartet und
 *      das Ergebnis zurückgibt
 * 
 * Zusätzlich dazu wird die konkrete Funktion anfangs deklariert.
 * Benutzung ist wie folgt möglich:
 * @code
 * 	// deklariert die Schnittstelle einer Funktion ´long fib(long)´
 * 	TASK(long, fib, long);
 * @endcode
 * 
 * @param TYPE  Rückgabetyp der Funktion
 * @param NAME  Name der Funktion
 * @param ARG   Parametertyp der Funktion
 */
#define DECL_ARG(ARG) ARG UID(ARG);
#define DO_NOTHING(ARG) ARG
#define POINT_ARG(ARG) data->UID(ARG)
#define AWAIT_ARG(ARG) ARG UID(ARG)
#define ASSIGN_ARG(ARG) .UID(ARG) = UID(ARG)
#define APPEND_COMMA(ARG) ARG,
#define TASK(TYPE, NAME, ...) \
    TYPE NAME(__VA_ARGS__); \
    \
    typedef struct { \
        Future fut;  \
        FOREACH(DO_NOTHING,DECL_ARG,(__VA_ARGS__)) \
        TYPE   res;  \
    } NAME ## _fut;  \
    \
    static void NAME ## Thunk(void *args) { \
        NAME ## _fut *data = args;          \
        data->res = NAME(FOREACH(APPEND_COMMA,POINT_ARG,(__VA_ARGS__)));        \
    } \
    static inline NAME ## _fut NAME ## Future(FOREACH(APPEND_COMMA,AWAIT_ARG,(__VA_ARGS__))) { \
        return (NAME ## _fut) {                          \
            .fut = { .fn = &NAME ## Thunk, .status=FUT_WAITING},             \
            FOREACH(APPEND_COMMA,ASSIGN_ARG,(__VA_ARGS__))                                   \
        };                                               \
    } \
    static inline NAME ## _fut* NAME ## Async(NAME ## _fut *future) { \
        return tpAsync(&future->fut), future;                         \
    } \
    static inline TYPE NAME ## Await(NAME ## _fut *future) { \
        return tpAwait(&future->fut), future->res;           \
    }

#endif