-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathcours05.html
341 lines (318 loc) · 11.7 KB
/
cours05.html
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Cours n°5 : Processus légers<br>(threads)</title>
<link rel="stylesheet" href="https://vpoupet.github.io/myriad/slides/slides.css">
<link rel="stylesheet" href="https://vpoupet.github.io/myriad/slides/themes/myriad/myriad.css">
<link rel="stylesheet" href="highlight.css">
</head>
<body>
<section class="title">
<h1>Cours n°5 :<br>Processus légers<br>(threads)</h1>
<div class="context">Réseau et Prog. Bas Niveau</div>
<div class="author">Victor Poupet</div>
<time>2024-03-18</time>
</section>
<section>
<h1>Processus légers</h1>
<div class="only" data-step="0">
<p class="skip">Si l'on veut exécuter plusieurs tâches en parallèle, on peut utiliser des processus différents</p>
<ul>
<li>la création d'un processus est une opération coûteuse</li>
<li>chaque processus occupe un segment de mémoire séparée</li>
<li>le code à exécuter est copié dans chaque nouveau processus</li>
<li>la communication entre processus est difficile (tubes, fichiers, sockets, etc.)</li>
</ul>
</div>
<div class="only">
<p class="skip">
Pour exécuter plusieurs instances de la même tâche en parallèle, on peut utiliser des <em>threads</em> (processus légers) à la place des processus</p>
<ul>
<li>Un unique processus peut exécuter plusieurs threads</li>
<li>
L'ordonnanceur gère les threads d'un même processus comme des tâches séparées (potentiellement sur des processeurs différents)</li>
<li>Les threads sont terminés lorsque le processus termine</li>
</ul>
</div>
</section>
<section>
<h1>Processus légers</h1>
<div class="side">
<div class="center"><img class="only" src="cours05/cours05-thread-01.svg" style="width: 80%" data-step="0" alt="threads"><img class="only" src="cours05/cours05-thread-02.svg" style="width: 80%" alt="threads"></div>
</div>
<div class="side">
<ul>
<li>
Chaque thread a ses propres variables locales, mais elles sont toutes dans la pile du processus (un thread a donc accès à la pile des autres)</li>
<li>
Les threads d'un processus partagent les segments <code>text</code> et <code>data</code> du processus, ainsi que le tas</li>
<li>
Chaque thread a ses propres pointeurs de pile et d'exécution, ainsi que l'état des registres du processeur</li>
</ul>
</div>
</section>
<section>
<h1>En C</h1>
<div class="side">
<pre><code class="cpp">#include <pthread.h>
int <span class="highlight">pthread_create</span>(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
int <span class="highlight">pthread_join</span>(pthread_t thread, void **value_ptr)
void <span class="highlight">pthread_exit</span>(void *value_ptr)</code></pre>
</div>
<div class="side">
<p>La bibliothèque <code>pthread</code> permet de créer et gérer des threads dans un processus</p>
<ul>
<li class="uncover"><code>pthread_create</code> pour démarrer un nouveau thread
<ul>
<li><code>start_routine</code> est la fonction à exécuter dans le thread</li>
<li><code>start_routine</code> prend un unique argument <code>arg</code> de type <code>void*</code></li>
</ul>
</li>
<li class="uncover"><code>pthread_join</code> pour attendre la fin d'un thread en cours
<ul>
<li><code>value_ptr</code> est un pointeur où écrire le résultat de la fonction du thread qui a terminé</li>
</ul>
</li>
<li class="uncover">
<code>pthread_exit</code> permet de terminer un thread (appelée automatiquement si la fonction <code>start_routine</code> termine)</li>
</ul>
</div>
</section>
<section>
<h1>Exemple</h1>
<div class="side">
<pre><code class="cpp only" data-step="0">#include <stdio.h>
#define NB_CASES 1000
void tache(int *tab) {
int i;
for (i = 0; i < NB_CASES; i++) {
tab[i]=i*i;
}
}</code><code class="cpp only">#include <stdio.h>
#define NB_CASES 1000
#define NB_THREADS 4
void tache(int deb, int fin, int *tab) {
int i;
for (i = deb; i < fin; i++) {
tab[i]=i*i;
}
}</code><code class="cpp only">#include <pthread.h>
#include <stdio.h>
#define NB_CASES 1000
#define NB_THREADS 4
struct ThreadArgs {
int debut;
int fin;
int *tab;
};
void* tache(void* args) {
struct ThreadArgs *a = args;
int i;
for (i = a->debut; i < a->fin; i++) {
a->tab[i]=i*i;
}
return NULL;
}</code></pre>
</div>
<div class="side">
<pre><code class="cpp only" data-step="0">int main(void) {
int i, tab[NB_CASES];
tache(tab);
for (i = 0; i < NB_CASES; i++) {
printf("%d ", tab[i]);
}
return 0;
}</code><code class="cpp only">int main(void) {
int i, tab[NB_CASES];
for (i = 0; i < NB_THREADS; i++) {
debut = i * NB_CASES / NB_THREADS;
fin = (i+1) * NB_CASES / NB_THREADS;
tache(debut, fin, tab);
}
for (i = 0; i < NB_CASES; i++) {
printf("%d ", tab[i]);
}
return 0;
}</code><code class="cpp only">int main(void) {
int i, tab[NB_CASES];
struct ThreadArgs args[NB_THREADS];
pthread_t threads[NB_THREADS];
for (i = 0; i < NB_THREADS; i++) {
args[i].debut = i * NB_CASES / NB_THREADS;
args[i].fin = (i+1) * NB_CASES / NB_THREADS;
args[i].tab = tab;
pthread_create(&threads[i], NULL, tache, &args[i]);
}
for (i = 0; i < NB_THREADS; i++) {
pthread_join(threads[i], NULL);
}
for (i = 0; i < NB_CASES; i++) {
printf("%d ", tab[i]);
}
return 0;
}</code></pre>
</div>
</section>
<section>
<h1>Concurrence</h1>
<div class="side">
<pre><code class="cpp">#include <pthread.h>
#include <stdio.h>
#define NB_THREADS 4
void* incr(void *arg) {
int i;
int *p = arg;
for (i = 0; i < 10000; i++) {
<span class="highlight">(*p)++</span>;
}
return NULL;
}
int main(void) {
int i, <span class="highlight">c=0</span>;
pthread_t threads[NB_THREADS];
for (i = 0; i < NB_THREADS; i++) {
pthread_create(&threads[i], NULL, incr, <span class="highlight">&c</span>); }
for (i = 0; i < NB_THREADS; i++) {
pthread_join(threads[i], NULL); }
printf("%d\n", c);
return 0;
}</code></pre>
</div>
<div class="side">
<p class="skip">Les threads partagent le même espace mémoire</p>
<ul>
<li>La modification d'une variable par un thread affecte les lectures sur cette variable par les autres threads</li>
<li>Problèmes d'accès concurrents</li>
<li>Il faut utiliser des mécanismes complexes pour s'assurer du bon déroulement du programme
<ul>
<li>sémaphores</li>
<li>mutex</li>
<li>barrières</li>
</ul>
</li>
</ul>
</div>
</section>
<section>
<h1>Thread safety</h1>
<div class="side">
<pre><code class="cpp">#include <pthread.h>
int incr() {
static int c = 0;
static pthread_mutex_t mutex;
mutex = PTHREAD_MUTEX_INITIALIZER;
// bloquer le verrou
pthread_mutex_lock(&mutex);
c++;
int r = c; // sauvegarder résultat
// libérer le verrou
pthread_mutex_unlock(&mutex);
return r;
}</code></pre>
</div>
<div class="side">
<ul>
<li>
assurer que les sections critiques (manipulations des variables partagées) ne soient accessibles que par un thread</li>
<li>attention aux blocages potentiels</li>
<li>difficile à tester (certains effets se produisent rarement)</li>
<li>les verrouillages ralentissent le parallélisme (goulot d'étranglement)</li>
</ul>
</div>
</section>
<section>
<h1>Réentrance</h1>
<div class="side">
<pre><code class="cpp only" data-step="0">// cette fonction est réentrante
void echange(int *x, int *y) {
int t;
t = *x;
*x = *y;
*y = t;
}</code><code class="cpp only">int t;
// cette fonction n'est pas réentrante
void echange(int *x, int *y) {
t = *x;
*x = *y;
*y = t;
}</code><code class="cpp only">int t;
// cette fonction est réentrante
void echange(int *x, int *y) {
int s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}</code></pre>
</div>
<div class="side">
<p>
Une fonction est dite <em>réentrante</em> si elle se comporte correctement lorsqu'elle est appelée pendant une exécution d'elle-même</p>
<ul>
<li>récursivité</li>
<li>exécution lors d'une interruption</li>
<li>exécution concurrente (threads)</li>
</ul>
</div>
</section>
<section>
<h1>Compilation</h1>
<div class="side">
<pre><code class="only" data-step="0">$ gcc prog.c -lpthread -D_REENTRANT</code><code class="only">$ gcc prog.c -pthread</code></pre>
</div>
<div class="side">
<ul class="only" data-step="0">
<li>
Pour utiliser les fonctions de la bibliothèque <code>pthread</code>, il faut demander au compilateur de lier l'exécutable à la bibliothèque : <code>-lpthread</code></li>
<li>
Par ailleurs, il faut indiquer que les fonctions doivent être réentrantes :<br> <code>-D_REENTRANT</code></li>
<ul>
<li>
certaines fonctions ont des variantes réentrantes (ex : <code>strtok_r</code> au lieu de <code>strtok</code>)</li>
<li>certaines macros sont remplacées par des fonctions (ex : <code>getc</code> et <code>putc</code>)</li>
<li>chaque thread dispose d'une instance différente de la variable <code>errno</code></li>
</ul>
</ul>
<ul class="only" data-start="1">
<li>
Lorsqu'elle est disponible, l'option <br><code>-pthread</code> se charge d'activer les options nécessaires, spécifiques au système courant (c'est la solution à préférer)</li>
<li>la plupart du temps, cela correspond à <br><code>-lpthread -D_REENTRANT</code></li>
</ul>
</div>
</section>
<section>
<h1><span style="display: inline-block; text-align: center; width: 50%">Processus</span><span style="display: inline-block; text-align: center; width: 50%">Thread</span></h1>
<div class="side">
<ul>
<li>Espace de mémoire virtuelle (apparaît comme connexe) séparée</li>
<li>Identifiant unique au niveau OS</li>
<li>Les processus sont disjoints</li>
<li>Peuvent fonctionner sur des machines distinctes</li>
</ul>
</div>
<div class="side">
<ul>
<li>Subdivision d'un processus</li>
<li>Mémoire partagée (même espace d'adressage virtuel)</li>
<li>Sur la même machine (potentiellement sur des processeurs différents)</li>
<li>Plus simple à créer</li>
<li>Tous les threads d'un même processus ont le même code</li>
</ul>
</div>
</section>
<script src="highlight.pack.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', (event) => {
hljs.configure({
languages: [], // disable automatic language detection
});
document.querySelectorAll('code').forEach((block) => {
hljs.highlightBlock(block);
});
});
</script>
<script src="https://vpoupet.github.io/myriad/slides/slides.js"></script>
<script src="https://vpoupet.github.io/myriad/slides/themes/myriad/myriad.js"></script>
</body>
</html>