1 |
scen 08/05/01 15:12:03 |
2 |
|
3 |
Added: l-posix3.xml |
4 |
Log: |
5 |
Initial commit: version 1.4, revision 1.6 of EN CVS |
6 |
|
7 |
Revision Changes Path |
8 |
1.1 xml/htdocs/doc/it/articles/l-posix3.xml |
9 |
|
10 |
file : http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/it/articles/l-posix3.xml?rev=1.1&view=markup |
11 |
plain: http://sources.gentoo.org/viewcvs.py/gentoo/xml/htdocs/doc/it/articles/l-posix3.xml?rev=1.1&content-type=text/plain |
12 |
|
13 |
Index: l-posix3.xml |
14 |
=================================================================== |
15 |
<?xml version='1.0' encoding="UTF-8"?> |
16 |
<!-- $Header: /var/cvsroot/gentoo/xml/htdocs/doc/it/articles/l-posix3.xml,v 1.1 2008/05/01 15:12:02 scen Exp $ --> |
17 |
<!DOCTYPE guide SYSTEM "/dtd/guide.dtd"> |
18 |
|
19 |
<guide link="/doc/it/articles/l-posix3.xml" disclaimer="articles" lang="it"> |
20 |
<title>Spiegazioni sui thread POSIX, parte 3</title> |
21 |
|
22 |
<author title="Autore"> |
23 |
<mail link="drobbins@g.o">Daniel Robbins</mail> |
24 |
</author> |
25 |
<author title="Traduzione"> |
26 |
<mail link="zanetti.massimo@×××××.com">Massimo Zanetti</mail> |
27 |
</author> |
28 |
|
29 |
<abstract> |
30 |
In questo articolo, l'ultimo di una serie di tre sui thread POSIX, Daniel dà |
31 |
una buona idea su come usare le variabili di condizione. Le variabili di |
32 |
condizione sono strutture di thread di POSIX che vi permettono di "risvegliare" |
33 |
i thread al verificarsi di certe condizioni. Potete pensare a loro come di una |
34 |
forma di signalling thread sicura. Daniel riempie l'articolo usando tutto |
35 |
quello che avete imparato fino ad adesso per sviluppare applicazioni work crew |
36 |
multi-thread. |
37 |
</abstract> |
38 |
|
39 |
|
40 |
<!-- La versione originale di questo articolo è stata pubblicata su |
41 |
IBM developerWorks, ed è di proprietà della Westtech Information |
42 |
Services.Questo documento è una versione aggiornata dell'articolo |
43 |
originale, e contiene diversi miglioramenti fatti dal Gentoo Linux |
44 |
Documentation Team |
45 |
--> |
46 |
|
47 |
<version>1.4</version> |
48 |
<date>2005-10-09</date> |
49 |
|
50 |
<chapter> |
51 |
<title>Migliorare l'efficenza con le variabili di condizione</title> |
52 |
<section> |
53 |
<title>Le variabili di condizione spiegate</title> |
54 |
<body> |
55 |
|
56 |
<p> |
57 |
Ho finito il mio <uri link="/doc/it/articles/l-posix2.xml">articolo |
58 |
precedente</uri> descrivendo un particolare dilemma su come faccia un thread a |
59 |
gestire una situazione in cui sta aspettando che una determinata condizione |
60 |
diventi vera. Potrebbe ripetutamente bloccare /sbloccare un mutex, controllando |
61 |
ogni volta per un certo valore una struttura dati condivisa. Ma questa è una |
62 |
perdita di tempo e di risorse e questa forma di busy polling è estremamente |
63 |
inefficiente. Il miglior modo per farlo è usare la chiamata pthread_cond_wait() |
64 |
per attendere che una determinata condizione diventi vera. |
65 |
</p> |
66 |
|
67 |
<p> |
68 |
E' importante capire che cosa pthread_cond_wait() faccia -- è il cuore del |
69 |
sistema di segnalazione dei thread di POSIX ed è anche la parte più difficile |
70 |
da capire. |
71 |
</p> |
72 |
|
73 |
<p> |
74 |
Per prima cosa consideriamo una scenario in cui un thread ha bloccato un |
75 |
mutex per leggere una lista linkata e la lista è vuota. Questo particolare |
76 |
thread non può fare nulla -- è scritto per togliere un nodo dalla lista e |
77 |
non ce ne sono disponibili. Così ecco cosa fa. |
78 |
</p> |
79 |
|
80 |
<p> |
81 |
Mentre continua a tenere il mutex bloccato, il nostro thread chiama |
82 |
pthread_cond_wait(&mycond,&mymutex) la chiamata a pthread_cond_wait() è |
83 |
abbastanza complessa così che affrontiamo ciascuna operazione un passo |
84 |
alla volta. |
85 |
</p> |
86 |
|
87 |
<p> |
88 |
La prima cosa che p_thread _cond_wait() fa è bloccare il mutex mymutex (così |
89 |
che gli altri thread possono modificare la lista linkata) e contemporaneamente |
90 |
aspetta la condizione mycond (così che pthread_cond_wait() si sveglia quando |
91 |
riceve un segnale da un altro thread). Ora che il mutex è sbloccato, altri |
92 |
thread possono accedere e modificare la lista linkata, possibilmente aggiungendo |
93 |
altri oggetti. |
94 |
|
95 |
</p> |
96 |
|
97 |
<p> |
98 |
A questo punto la chiamata pthread_cond_wait() non è ancora ritornata. |
99 |
Lo sbloccaggio del mutex avviene immediatamente, ma aspettare per la condizione |
100 |
mycond è normalmente un'operazione che blocca, ciò significa che il nostro |
101 |
thread va in sleep, senza consumare alcun ciclo di CPU fine al momento del |
102 |
risveglio. Questo è esattamente quello che vogliano che succeda. Il nostro |
103 |
thread è in sleep, aspettando che una determinata condizione diventi vera, |
104 |
senza fare nessun tipo di "busy polling" che sprecherebbe tempo di CPU. Dal |
105 |
punto di vista del nostro thread, sta semplicemente aspettando che ritorni la |
106 |
chiamata pthread_cond_wait(). |
107 |
</p> |
108 |
|
109 |
<p> |
110 |
Ora, per continuare con la spiegazione, diciamo che un altro thread |
111 |
(chiamiamolo "thread 2") blocchi mymutex e aggiunga un oggetto alla nostra |
112 |
lista linkata. Immediatamente dopo aver sbloccato il mutex, il thread 2 chiama |
113 |
la funzione pthread_cond_broadcast(&mycond). Facendo questo, il thread 2 fa |
114 |
svegliare immediatamente tutti quei thread che aspettavano la variabile di |
115 |
condizione mycond. Questo significa che il nostro primo thread (che è nel mezzo |
116 |
di una chiamata pthread_cond_wait()) adesso si sveglia. |
117 |
</p> |
118 |
|
119 |
<p> |
120 |
Ora diamo un'occhiata a cosa succede al nostro primo thread. Dopo che il thread |
121 |
2 ha chiamato pthread_cond_broadcast(&mymutex) potreste pensare che la |
122 |
pthread_cond_wait() del thread 1 ritorni immediatamente. Non è così! Invece, |
123 |
pthread_cond_wait() eseguirà un'ultima operazione: ribloccare mymutex. Una |
124 |
volta che pthread_cond_wait() ha il blocco, allora ritornerà e permetterà a |
125 |
thread 1 di continuare l'esecuzione. A quel punto, può immediatamente |
126 |
controllare la lista per qualsiasi cambiamento degno di nota. |
127 |
</p> |
128 |
|
129 |
</body> |
130 |
</section> |
131 |
<section> |
132 |
<title>Fermati e riguarda!</title> |
133 |
<body> |
134 |
|
135 |
<!-- These bits do not make any sense to me, commented out |
136 |
|
137 |
<pre caption="queue.h"> |
138 |
pthread_cond_t mycond; |
139 |
</pre> |
140 |
|
141 |
<pre caption="control.h"> |
142 |
pthread_cond_t mycond; |
143 |
|
144 |
pthread_cond_init(&mycond,NULL); |
145 |
|
146 |
pthread_cond_destroy(&mycond); |
147 |
|
148 |
pthread_cond_wait(&mycond, &mymutex); |
149 |
|
150 |
pthread_cond_broadcast(&mycond); |
151 |
|
152 |
pthread_cond_signal(&mycond); |
153 |
</pre> |
154 |
--> |
155 |
<pre caption="queue.h"> |
156 |
/* queue.h |
157 |
<comment>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc. |
158 |
** Autore: Daniel Robbins |
159 |
** Data: 16 Giugno 2000</comment> |
160 |
*/ |
161 |
typedef struct node { |
162 |
struct node *next; |
163 |
} node; |
164 |
typedef struct queue { |
165 |
node *head, *tail; |
166 |
} queue; |
167 |
void queue_init(queue *myroot); |
168 |
void queue_put(queue *myroot, node *mynode); |
169 |
node *queue_get(queue *myroot); |
170 |
</pre> |
171 |
|
172 |
<pre caption="queue.c"> |
173 |
/* queue.c |
174 |
<comment>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc. |
175 |
** Autore: Daniel Robbins |
176 |
** Data: 16 Giugno 2000 |
177 |
** |
178 |
** Questo insieme di funzioni di coda era originariamente conscio dei thread. |
179 |
** Io l'ho scritto per fare in modo che questo insieme di routine di coda sia |
180 |
** thread-ignorante (giusto un generico noioso ma molto veloce insieme di |
181 |
** routine di coda). Perché questo cambiamento? Perché ha più senso aver il |
182 |
** supporto ai thread come un add-on opzionale. Si consideri una situazione in |
183 |
** cui si vogliono aggiungere 5 nodi alla coda. Con la versione con i thread, |
184 |
** ciascuna chiamata a queue_port() automaticamente bloccherebbe o sbloccherebbe |
185 |
** il mutex della coda 5 volte-- c'è molto overhead non necessario. |
186 |
** Tuttavia, muovendo la parte relativa al thread al di fuori delle routine di |
187 |
** coda, il chiamante può bloccare il mutex una volta all'inizio, dopo di che |
188 |
** inserisce i cinque oggetti, ed infine lo sblocca. Spostare il codice di |
189 |
** blocco/sblocco al di fuori delle funzioni di coda permette ottimizzazioni |
190 |
** altrimenti impossibili. Rende inoltre questo codice utile ad applicazioni |
191 |
** senza thread. |
192 |
** |
193 |
** Possiamo facilmente rendere thread-enable questa struttura dati usando il |
194 |
** tipo data-control definito in control.c e control.h.</comment> */ |
195 |
#include <stdio.h> |
196 |
#include "queue.h" |
197 |
void queue_init(queue *myroot) { |
198 |
myroot->head=NULL; |
199 |
myroot->tail=NULL; |
200 |
} |
201 |
void queue_put(queue *myroot,node *mynode) { |
202 |
mynode->next=NULL; |
203 |
if (myroot->tail!=NULL) |
204 |
myroot->tail->next=mynode; |
205 |
myroot->tail=mynode; |
206 |
if (myroot->head==NULL) |
207 |
myroot->head=mynode; |
208 |
} |
209 |
node *queue_get(queue *myroot) { |
210 |
//get from root |
211 |
node *mynode; |
212 |
mynode=myroot->head; |
213 |
if (myroot->head!=NULL) |
214 |
myroot->head=myroot->head->next; |
215 |
return mynode; |
216 |
} |
217 |
</pre> |
218 |
|
219 |
<pre caption="control.h"> |
220 |
#include <pthread.h> |
221 |
typedef struct data_control { |
222 |
pthread_mutex_t mutex; |
223 |
pthread_cond_t cond; |
224 |
int active; |
225 |
} data_control; |
226 |
</pre> |
227 |
|
228 |
<pre caption="control.c"> |
229 |
/* control.c |
230 |
<comment>** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc. |
231 |
** Autore: Daniel Robbins |
232 |
** Data: 16 Giugno 2000 |
233 |
** |
234 |
** Queste routine forniscono un modo semplice per rendere qualsiasi tipo di |
235 |
** strutture dati thread-aware. Semplicemente associamo una struttura |
236 |
** data-control con la struttura dati (creando una nuova struttura, ad |
237 |
** esempio). Dopo, semplicemente, bloccano e sbloccano il mutex, o |
238 |
** (aspetta/segnala/trasmette) la variabile di condizione nella struttura |
239 |
** data_control come necessario. |
240 |
** |
241 |
** Le strutture data_control contengono un int chiamato "active". Lo scopo di |
242 |
** questo int è di essere usato per uno specifico tipo di progetto |
243 |
** multi-threaded, dove ciascun thread controlla lo stato dell'"active" ogni |
244 |
** volta che blocca il mutex. Se "active" è 0 il thread sa che invece di fare |
245 |
** la sua normale routine deve a sua volta fermarsi. |
246 |
** Se active è 1, dovrebbe continuare come sempre. Così, impostando active a |
247 |
** 0, un thread di controllo può facilmente informare un thread work crew di |
248 |
** spegnersi invece che processare nuovi job. Si usino le funzione |
249 |
** control_activate() e control_deactivate(), che trasmetteranno anche sulla |
250 |
** variabile di condizione della struttura data_control, così che tutti i |
251 |
** thread fermi in pthread_cond_wait() si sveglino, abbiano modo di notare il |
252 |
** cambiamento ed infine terminino.</comment>*/ |
253 |
#include "control.h" |
254 |
int control_init(data_control *mycontrol) { |
255 |
int mystatus; |
256 |
if (pthread_mutex_init(&(mycontrol->mutex),NULL)) |
257 |
return 1; |
258 |
if (pthread_cond_init(&(mycontrol->cond),NULL)) |
259 |
return 1; |
260 |
mycontrol->active=0; |
261 |
return 0; |
262 |
} |
263 |
int control_destroy(data_control *mycontrol) { |
264 |
int mystatus; |
265 |
if (pthread_cond_destroy(&(mycontrol->cond))) |
266 |
return 1; |
267 |
if (pthread_mutex_destroy(&(mycontrol->cond))) |
268 |
return 1; |
269 |
mycontrol->active=0; |
270 |
return 0; |
271 |
} |
272 |
int control_activate(data_control *mycontrol) { |
273 |
int mystatus; |
274 |
if (pthread_mutex_lock(&(mycontrol->mutex))) |
275 |
return 0; |
276 |
mycontrol->active=1; |
277 |
pthread_mutex_unlock(&(mycontrol->mutex)); |
278 |
pthread_cond_broadcast(&(mycontrol->cond)); |
279 |
return 1; |
280 |
} |
281 |
int control_deactivate(data_control *mycontrol) { |
282 |
int mystatus; |
283 |
if (pthread_mutex_lock(&(mycontrol->mutex))) |
284 |
return 0; |
285 |
mycontrol->active=0; |
286 |
pthread_mutex_unlock(&(mycontrol->mutex)); |
287 |
pthread_cond_broadcast(&(mycontrol->cond)); |
288 |
return 1; |
289 |
} |
290 |
</pre> |
291 |
|
292 |
</body> |
293 |
</section> |
294 |
<section> |
295 |
<title>Debug time</title> |
296 |
<body> |
297 |
|
298 |
<p> |
299 |
Un altro file misto prima di arrivare a quello grosso. Ecco <path>dbug.h</path>: |
300 |
</p> |
301 |
|
302 |
<pre caption="dbug.h"> |
303 |
#define dabort() \ |
304 |
{ printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); |
305 |
abort(); } |
306 |
</pre> |
307 |
|
308 |
<p> |
309 |
Usiamo questo codice per gestire errori irrecuperabili nel nostro codice work |
310 |
crew. |
311 |
</p> |
312 |
|
313 |
</body> |
314 |
</section> |
315 |
<section> |
316 |
<title>Il codice di work crew</title> |
317 |
<body> |
318 |
|
319 |
<p> |
320 |
Parlando del codice di work crew code, eccolo: |
321 |
</p> |
322 |
|
323 |
<pre caption="workcrew.c>"> |
324 |
#include <stdio.h> |
325 |
#include <stdlib.h> |
326 |
#include "control.h" |
327 |
#include "queue.h" |
328 |
#include "dbug.h" |
329 |
/* <comment>Il work_queue tiene i task per i differenti thread da |
330 |
completare.</comment>*/ |
331 |
struct work_queue { |
332 |
data_control control; |
333 |
queue work; |
334 |
} wq; |
335 |
/* <comment>Ho aggiunto un numero di job al work node. Normalmente il work node |
336 |
contiene ulteriori dati che hanno bisogno di essere processati.</comment>*/ |
337 |
typedef struct work_node { |
338 |
struct node *next; |
339 |
int jobnum; |
340 |
} wnode; |
341 |
/* <comment>La coda di cleanup conserva i thread formati. Prima che un threa |
342 |
d termini aggiunge se stesso a questa lista. Siccome il thread principale |
343 |
sta aspettando le modifiche di questa lista, si sveglia e "pulisce" il |
344 |
thread appena terminato. </comment>*/ |
345 |
struct cleanup_queue { |
346 |
data_control control; |
347 |
queue cleanup; |
348 |
} cq; |
349 |
/* <comment>Ho aggiunto un numero al thread (ad uso debugging/studio) e una |
350 |
thread id al nodo cleanup. Il nodo cleanup viene passato al nuovo thread |
351 |
nell'avvio, e un attimo prima che il thread si fermi, attacca il nodo cleanup |
352 |
alla coda di cleanup. Il thread principale monitora la coda di cleanup ed è |
353 |
quello che esegue il necessario cleanup.</comment> */ |
354 |
typedef struct cleanup_node { |
355 |
struct node *next; |
356 |
int threadnum; |
357 |
pthread_t tid; |
358 |
} cnode; |
359 |
void *threadfunc(void *myarg) { |
360 |
wnode *mywork; |
361 |
cnode *mynode; |
362 |
mynode=(cnode *) myarg; |
363 |
pthread_mutex_lock(&wq.control.mutex); |
364 |
while (wq.control.active) { |
365 |
while (wq.work.head==NULL && wq.control.active) { |
366 |
pthread_cond_wait(&wq.control.cond, &wq.control.mutex); |
367 |
} |
368 |
if (!wq.control.active) |
369 |
break; |
370 |
//we got something! |
371 |
mywork=(wnode *) queue_get(&wq.work); |
372 |
pthread_mutex_unlock(&wq.control.mutex); |
373 |
//perform processing... |
374 |
printf("Thread number %d processing job |
375 |
%d\n",mynode->threadnum,mywork->jobnum); |
376 |
free(mywork); |
377 |
pthread_mutex_lock(&wq.control.mutex); |
378 |
} |
379 |
pthread_mutex_unlock(&wq.control.mutex); |
380 |
pthread_mutex_lock(&cq.control.mutex); |
381 |
queue_put(&cq.cleanup,(node *) mynode); |
382 |
pthread_mutex_unlock(&cq.control.mutex); |
383 |
pthread_cond_signal(&cq.control.cond); |
384 |
printf("thread %d shutting down...\n",mynode->threadnum); |
385 |
return NULL; |
386 |
|
387 |
} |
388 |
#define NUM_WORKERS 4 |
389 |
int numthreads; |
390 |
void join_threads(void) { |
391 |
cnode *curnode; |
392 |
printf("joining threads...\n"); |
393 |
while (numthreads) { |
394 |
pthread_mutex_lock(&cq.control.mutex); |
395 |
/* <comment>sotto, dormiamo fino a che c'è veramente un nodo cleanup. |
396 |
Questo si prende cura di ogni falso risveglio ... Anche se usciamo da |
397 |
pthread_cond_wait(), non riteniamo che la condizione che stiamo |
398 |
aspettando sia |
399 |
vera.</comment>*/ |
400 |
while (cq.cleanup.head==NULL) { |
401 |
pthread_cond_wait(&cq.control.cond,&cq.control.mutex); |
402 |
} |
403 |
/* <comment>a questo punto, conserviamo il mutex e c'è un item nella lista |
404 |
che dobbiamo processare. Per prima cosa rimuoviamo il nodo dalla |
405 |
coda. Poi, chiamiamo pthread_join() sulla parte immagazzinata nel node. Quando |
406 |
pthread_join() ritorna, è tutto pulito dopo il thread. Solo allora facciamo |
407 |
un free() al node, decrementiamo il numero di thread addizionali |
408 |
che dobbiamo aspettare e ripetiamo l'intero processo, se necessario</comment> */ |
409 |
curnode = (cnode *) queue_get(&cq.cleanup); |
410 |
pthread_mutex_unlock(&cq.control.mutex); |
411 |
pthread_join(curnode->tid,NULL); |
412 |
printf("joined with thread %d\n",curnode->threadnum); |
413 |
free(curnode); |
414 |
numthreads--; |
415 |
} |
416 |
} |
417 |
int create_threads(void) { |
418 |
int x; |
419 |
cnode *curnode; |
420 |
for (x=0; x<NUM_WORKERS; x++) { |
421 |
curnode=malloc(sizeof(cnode)); |
422 |
if (!curnode) |
423 |
return 1; |
424 |
curnode->threadnum=x; |
425 |
if (pthread_create(&curnode->tid, NULL, threadfunc, (void *) curnode)) |
426 |
return 1; |
427 |
printf("created thread %d\n",x); |
428 |
numthreads++; |
429 |
} |
430 |
return 0; |
431 |
} |
432 |
void initialize_structs(void) { |
433 |
numthreads=0; |
434 |
if (control_init(&wq.control)) |
435 |
dabort(); |
436 |
queue_init(&wq.work); |
437 |
if (control_init(&cq.control)) { |
438 |
control_destroy(&wq.control); |
439 |
dabort(); |
440 |
} |
441 |
queue_init(&wq.work); |
442 |
control_activate(&wq.control); |
443 |
} |
444 |
void cleanup_structs(void) { |
445 |
control_destroy(&cq.control); |
446 |
control_destroy(&wq.control); |
447 |
} |
448 |
int main(void) { |
449 |
int x; |
450 |
wnode *mywork; |
451 |
initialize_structs(); |
452 |
/* CREATION */ |
453 |
|
454 |
if (create_threads()) { |
455 |
printf("Error starting threads... cleaning up.\n"); |
456 |
join_threads(); |
457 |
dabort(); |
458 |
} |
459 |
pthread_mutex_lock(&wq.control.mutex); |
460 |
for (x=0; x<16000; x++) { |
461 |
mywork=malloc(sizeof(wnode)); |
462 |
if (!mywork) { |
463 |
printf("ouch! can't malloc!\n"); |
464 |
break; |
465 |
} |
466 |
mywork->jobnum=x; |
467 |
queue_put(&wq.work,(node *) mywork); |
468 |
} |
469 |
pthread_mutex_unlock(&wq.control.mutex); |
470 |
pthread_cond_broadcast(&wq.control.cond); |
471 |
printf("sleeping...\n"); |
472 |
sleep(2); |
473 |
printf("deactivating work queue...\n"); |
474 |
control_deactivate(&wq.control); |
475 |
/* CLEANUP */ |
476 |
join_threads(); |
477 |
cleanup_structs(); |
478 |
} |
479 |
</pre> |
480 |
|
481 |
</body> |
482 |
</section> |
483 |
<section> |
484 |
<title>Attraverso il codice</title> |
485 |
<body> |
486 |
|
487 |
<p> |
488 |
Adesso è ora di fare una veloce passeggiata attraverso il codice. La prima |
489 |
struttura definita è chiamata "wq", e contiene un data_control e una queue |
490 |
header. La struttura data_control viene usata per arbitrare l'accesso |
491 |
all'intera coda, inclusi i nodi. Il nostro prossimo lavoro è definire |
492 |
i reali nodi di lavoro. Per mantenere il codice snello in maniera da |
493 |
farlo entrare in questo articolo tutto quello che c'è qui è un job number. |
494 |
</p> |
495 |
|
496 |
<p> |
497 |
Successivamente creiamo una coda per cleanup. I commenti mostrano come ciò |
498 |
funzioni. OK, per ora saltiamo le chiamate a threadfunc(), join_threads(), |
499 |
create_threads() e initialize_structs(), e saltiamo alla main(). La prima |
500 |
cosa che facciamo è inizializzare le nostre strutture -- questo include |
501 |
l'inizializzare la nostra data_controls e le code, come anche attivare la |
502 |
nostra coda di lavoro. |
503 |
</p> |
504 |
|
505 |
</body> |
506 |
</section> |
507 |
<section> |
508 |
<title>Cleanup special</title> |
509 |
<body> |
510 |
|
511 |
<p> |
512 |
Ora è il momento di inizializzare i nostri thread. Se si guarda alla chiamata |
513 |
alla nostra create_threads(), tutto sembra piuttosto normale...eccetto una cosa. |
514 |
Si noti che noi allochiamo un nodo cleanup, inizializziamo il suo numero di |
515 |
thread e componenti TID. Passiamo inoltre un nodo cleanup a ciascun nuovo worker |
516 |
thread come argomento iniziale.perché lo facciamo? |
517 |
</p> |
518 |
|
519 |
<p> |
520 |
Perché quando un worker thread esce, attaccherà il suo nodo cleanup alla coda |
521 |
cleanup e terminerà. In seguito il nostro thread principale noterà questa |
522 |
aggiunta alla coda cleanup (grazie all'uso di una variabile di condizione) e |
523 |
dequeue il nodo. Siccome la TID (id del thread) è salvata nel nodo cleanup, il |
524 |
nostro thread principale saprà esattamente quale thread terminare. Allora il |
525 |
nostro thread principale chiamerà pthread_join(tid), e con il worker si |
526 |
attaccherà al thread appropriato. Se non facessimo questo tipo di controllo, il |
527 |
nostro thread principale si dovrebbe attaccare ai worker thread in modo |
528 |
arbitrario. Presumibilmente nell'ordine in cui sono stati creati. Siccome i |
529 |
thread non devono necessariamente terminare in quest'ordine, il nostro thread |
530 |
principale potrebbe stare aspettando di unirsi con un thread mentre avrebbe |
531 |
potuto unirsi con altri dieci. Riuscite a vedere come questa scelta di |
532 |
progetto possa realmente velocizzare il nostro codice di spegnimento |
533 |
specialmente se si usano centinaia di worker thread? |
534 |
</p> |
535 |
|
536 |
</body> |
537 |
</section> |
538 |
<section> |
539 |
<title>Creare lavoro</title> |
540 |
<body> |
541 |
|
542 |
<p> |
543 |
Ora che abbiamo fatto partire i nostri worker thread (e che stanno facendo |
544 |
andare le loro threadfunc(), di cui parleremo tra poco), il nostro thread |
545 |
principale incomincia ad inserire oggetti all'interno della coda di work. Per |
546 |
prima cosa, blocca il controllo mutex di wq, e poi alloca 16000 pacchetti work |
547 |
inserendoli ad uno ad uno all'interno della coda. Dopo di che, |
548 |
pthread_cond_broadcast() è chiamata così che, qualsiasi thread dormiente viene |
549 |
svegliato è puo fare il lavoro. Allora il nostro thread principale dorme per |
550 |
due secondi, dopo di che disattiva la work queue dicendo ai worker thread di |
551 |
terminare. Quindi il nostro thread principale chiama le funzioni join_threads() |
552 |
per pulire tutti i worker thread. |
553 |
</p> |
554 |
|
555 |
</body> |
556 |
</section> |
557 |
<section> |
558 |
<title>threadfunc()</title> |
559 |
<body> |
560 |
|
561 |
<p> |
562 |
E' ora di guardare threadfunc(), il codice che ciascun work thread esegue. |
563 |
Quando un worker thread inizia, immediatamente blocca il mutex della work queue, |
564 |
prende un node work (se disponibile) e lo processa. Se non c'è un work |
565 |
disponibile pthread_cond_wait() viene chiamato. Noterete che è chiamato |
566 |
in un ciclo while() molto stretto e questo è molto importante. Quando ci |
567 |
si sveglia da una chiamata pthread_cond_wait(), non si dovrebbe mai |
568 |
presupporre che la nostra condizione sia assolutamente vera, probabilmente lo |
569 |
sarà ma potrebbe anche non esserlo. Il ciclo while() forza pthread_cond_wait() |
570 |
ad essere richiamato se accadesse che il thread venisse erroneamente svegliato |
571 |
e la lista fosse vuota. |
572 |
</p> |
573 |
|
574 |
<p> |
575 |
Se c'è un work node, semplicemente stampiamo il suo numero di job, lo liberiamo |
576 |
e usciamo. Nella realtà il codice farebbe qualche cosa di più sostanziale. Alla |
577 |
fine del ciclo while(), blocchiamo il mutex così che possiamo controllare la |
578 |
variabile attiva come anche controllare nuovi work node all'inizio del ciclo. Se |
579 |
si segue il codice si troverà che il wq.control.active è 0, il ciclo while() |
580 |
sarà terminato e il codice di cleanup alla fine di threadfunc() ricomincerà. |
581 |
</p> |
582 |
|
583 |
<p> |
584 |
La parte del worker thread è abbastanza interessante. Primo sblocca la |
585 |
work_queue, poiché se il mutex è bloccato phread_cond_wait() ritorna. Dopo di |
586 |
che prende un lock sulla code cleanup, aggiunge il nostro cleanup node (che |
587 |
contiene la nostra TID, che il thread principale userà per la sua chiamata a |
588 |
pthread_join(), e dopo sbloccherà la cleanup queue. Dopo di che segnala a cq |
589 |
waiters (pthread_cond_signal(&cq.control.cond)) così che il thread |
590 |
principale sa che c'è un nuovo nodo da processare. Non usiamo |
591 |
pthread_cond_broadcast() perché non è necessario -- solamente un thread (il |
592 |
thread principale) sta aspettando nuove entry nella coda di cleanup. Il nostro |
593 |
worker thread stampa un messaggio di spegnimento e poi termina aspettando di |
594 |
essere pthread_joined() dal thread principale quando chiama join_threads(). |
595 |
</p> |
596 |
|
597 |
</body> |
598 |
</section> |
599 |
<section> |
600 |
<title>join_threads()</title> |
601 |
<body> |
602 |
|
603 |
<p> |
604 |
Se volete vedere un semplice esempio di come le variabili di condizione |
605 |
dovrebbero essere usate, data un'occhiata alla funzione join_threads(). Mentre |
606 |
abbiamo ancora worker thread in esistenza, join_threads() cicla, |
607 |
aspettando nuovi nodi cleanup nella nostra coda cleanup. Se c'è un nuovo |
608 |
nodo, si dequeue il nodo, sblocca la cleanup queue (così che altri nodi |
609 |
di cleanup possano essere aggiungi dai nostri worker thread), si unisce con |
610 |
il nuovo thread (usando la TID memorizzata nel nodo cleanup), libera il nodo |
611 |
cleanup, decrementa il numero di thread "li fuori" e continua. |
612 |
</p> |
613 |
|
614 |
</body> |
615 |
</section> |
616 |
<section> |
617 |
<title>Riassumendo</title> |
618 |
<body> |
619 |
|
620 |
<p> |
621 |
Siamo arrivati alla fine della serie "Spiegazione sui thread POSIX, parte |
622 |
tre", e spero che ora siate pronti ad aggiungere codice multithreaded alle |
623 |
vostre applicazioni. Per maggiori informazioni vogliate guardare la sezione |
624 |
<uri link="#resources">Resources</uri>, che contiene anche una tarball di tutti |
625 |
i sorgenti usati in questo articolo. Alla prossima serie! |
626 |
</p> |
627 |
|
628 |
</body> |
629 |
</section> |
630 |
</chapter> |
631 |
|
632 |
<chapter id="resources"> |
633 |
<title>Resources</title> |
634 |
<section> |
635 |
<body> |
636 |
|
637 |
<ul> |
638 |
<li> |
639 |
E' disponibile un <uri link="/doc/en/files/l-posix-thread-3.tar.gz">tarball |
640 |
dei sorgenti</uri> usati in questo articolo. |
641 |
</li> |
642 |
<li> |
643 |
Leggete gli articoli di Daniel, Spiegazioni sui thread POSIX <uri |
644 |
link="l-posix1.xml">Parte 1</uri> e <uri link="l-posix2.xml">Parte |
645 |
2</uri>. |
646 |
</li> |
647 |
<li> |
648 |
Date sempre un'occhiata all'amichevole pagina del manuale LINUX di pthread |
649 |
(<c>man -k pthread</c>). |
650 |
</li> |
651 |
<li> |
652 |
Per una terapia d'urto raccomando questo libro: <uri |
653 |
link="http://search.borders.com/fcgi-bin/db2www/search/search.d2w/Details?&mediaType=Book&prodID=2362607"> |
654 |
Programming with POSIX Threads</uri>, di David R. Butenhof (Addison-Wesley, |
655 |
1997). Questo è presumibilmente il miglior libro sui thread POSIX |
656 |
disponibile. |
657 |
</li> |
658 |
<li> |
659 |
I thread POSIX sono anche affrontati in questo libro: <uri |
660 |
link="http://search.borders.com/fcgi-bin/db2www/search/search.d2w/Details?&mediaType=Book&prodID=2362607"> |
661 |
UNIX Network Programming - Networking APIs: Sockets and XTI</uri>, di W. |
662 |
Richard Stevens (Prentice Hall, 1997). Questo è un classico libro, ma non |
663 |
copre i thread così in dettaglio come invece Programming with POSIX Threads |
664 |
fa. |
665 |
</li> |
666 |
<li> |
667 |
Guardate la documentazione su: <uri |
668 |
link="http://metalab.unc.edu/pub/Linux/docs/faqs/Threads-FAQ/html/">Linux |
669 |
threads</uri>, di Sean Walton, KB7rfa. |
670 |
</li> |
671 |
<li> |
672 |
Consultate un <uri |
673 |
link="http://www.math.arizona.edu/swig/pthreads/threads.html">tutorial</uri> |
674 |
sui thread POSIX di Mark Hays, Università dell'Arizona. |
675 |
</li> |
676 |
<li> |
677 |
In <uri link="http://hwaci.com/sw/pttcl/pttcl.html">An Introduction to |
678 |
Pthreads-Tcl</uri>, guardate i cambiamenti a Tcl che gli permettono di |
679 |
essere usato con i thread POSIX. |
680 |
</li> |
681 |
<li> |
682 |
Un'altro tutorial, <uri |
683 |
link="http://dis.cs.umass.edu/~wagner/threads_html/tutorial.html">Getting |
684 |
Started with POSIX Threads</uri>, di Tom Wagner e Don Towsley del |
685 |
dipartimento di Computer Science presso l'Università del |
686 |
Massachusetts,Amherst. |
687 |
</li> |
688 |
<li> |
689 |
<uri link="http://moss.csc.ncsu.edu/~mueller/pthreads/">FSU PThreads</uri> è |
690 |
una libreria C che implementa i thread POSIX per SunOS 4.1.x,Solaris 2.x, |
691 |
SCO UNIX, FreeBSD, Linux, e DOS. |
692 |
</li> |
693 |
<li> |
694 |
Fate riferimento all'home page <uri |
695 |
link="http://www.sai.msu.su/sal/C/2/PCTHREADS.html"> per thread POSIX e DCE |
696 |
</uri> per Linux. |
697 |
</li> |
698 |
<li> |
699 |
Guardate <uri link="http://pauillac.inria.fr/~xleroy/linuxthreads/">The |
700 |
LinuxThreads library</uri>. |
701 |
</li> |
702 |
<li> |
703 |
<uri link="http://www.users.itl.net.ua/~prool/proolix.html">Proolix</uri> è |
704 |
un semplice sistema operativo POSIX-compliant per i8086+ in continuo |
705 |
sviluppo. |
706 |
</li> |
707 |
</ul> |
708 |
|
709 |
</body> |
710 |
</section> |
711 |
</chapter> |
712 |
</guide> |
713 |
|
714 |
|
715 |
|
716 |
-- |
717 |
gentoo-commits@l.g.o mailing list |