forked from IUTInfoMontp-M2101/cours
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcours05.pug
316 lines (302 loc) · 10.9 KB
/
cours05.pug
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
extends coursXX.pug
append preamble
- date = "2019-03-11";
- title = "Cours n°5 :<br>Processus légers<br>(threads)";
block document
section.single
h1 Processus légers
.only(data-step=0)
p.skip Si l'on veut exécuter plusieurs tâches en parallèle, on peut utiliser des processus différents
ul
li la création d'un processus est une opération coûteuse
li chaque processus occupe un segment de mémoire séparée
li le code à exécuter est copié dans chaque nouveau processus
li la communication entre processus est difficile (tubes, fichiers, sockets, etc.)
.only
p.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
ul
li Un unique processus peut exécuter plusieurs threads
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 Les threads sont terminés lorsque le processus termine
section.split
h1 Processus légers
.side
.center
img.only(src="cours05/cours05-thread-01.svg" style="width: 80%" data-step="0" alt="threads")
img.only(src="cours05/cours05-thread-02.svg" style="width: 80%" alt="threads")
.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 Les threads d'un processus partagent les segments <code>text</code> et <code>data</code> du processus,
| ainsi que le tas
li Chaque thread a ses propres pointeurs de pile et d'exécution, ainsi que l'état des registres du
| processeur
section.split
h1 En C
.side
pre
code.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)
.side
p La bibliothèque <code>pthread</code> permet de créer et gérer des threads dans un processus
ul
li.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 cette fonction prend un unique argument <code>arg</code> de type <code>void*</code>
li.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.uncover <code>pthread_exit</code> permet de terminer un thread (appelée automatiquement si la fonction
| <code>start_routine</code> termine)
section.split
h1 Exemple
.side
pre
code.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.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.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;
| }
.side
pre
code.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.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.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;
| }
section.split
h1 Concurrence
.side
pre
code.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;
| }
.side
p.skip Les threads partagent le même espace mémoire
ul
li La modification d'une variable par un thread affecte les lectures sur cette variable par les autres threads
li Problèmes d'accès concurrents
li Il faut utiliser des mécanismes complexes pour s'assurer du bon déroulement du programme
ul
li sémaphores
li mutex
li barrières
section.split
h1 Thread safety
.side
pre
code.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;
| }
.side
ul
li assurer que les sections critiques (manipulations des variables partagées) ne soient accessibles que
| par un thread
li attention aux blocages potentiels
li difficile à tester (certains effets se produisent rarement)
li les verrouillages ralentissent le parallélisme (goulot d'étranglement)
section.split
h1 Réentrance
.side
pre
code.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.cpp.only
| int t;
|
| // cette fonction n'est pas réentrante
| void echange(int *x, int *y) {
| t = *x;
| *x = *y;
| *y = t;
| }
code.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;
| }
.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
ul
li récursivité
li exécution lors d'une interruption
li exécution concurrente (threads)
section.split
h1 Compilation
.side
pre
code.only(data-step=0)
| $ gcc prog.c -lpthread -D_REENTRANT
code.only
| $ gcc prog.c -pthread
.side
ul.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 Par ailleurs, il faut indiquer que les fonctions doivent être réentrantes :<br>
| <code>-D_REENTRANT</code>
ul
li certaines fonctions ont des variantes réentrantes (ex : <code>strtok_r</code> au lieu de
| <code>strtok</code>)
li certaines macros sont remplacées par des fonctions (ex : <code>getc</code> et <code>putc</code>)
li chaque thread dispose d'une instance différente de la variable <code>errno</code>
ul.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 la plupart du temps, cela correspond à <br><code>-lpthread -D_REENTRANT</code>
section.split
h1
span(style="display: inline-block; text-align: center; width: 50%") Processus
span(style="display: inline-block; text-align: center; width: 50%") Thread
.side
ul
li Espace de mémoire virtuelle (apparaît comme connexe) séparée
li Identifiant unique au niveau OS
li Les processus sont disjoints
li Peuvent fonctionner sur des machines distinctes
.side
ul
li Subdivision d'un processus
li Mémoire partagée (même espace d'adressage virtuel)
li Sur la même machine (potentiellement sur des processeurs différents)
li Plus simple à créer
li Tous les threads d'un même processus ont le même code