Da bi razumeo pojam "nit" moras razumeti dobro i pojam "proces". Zasto? - Nit je zapravo "laganiji" proces. Ukoliko znas osnovne stvari o procesima, onda znas da proces kao deo APLIKACIJE:
1) zna sve o resursima
APLIKACIJE
2) PID, PID od grupe kojoj pripada, UID, GID (APLIKACIJE)
3) zna sve environment varijable (APLIKACIJE)
4) radne direktorijume (APLIKACIJE)
5) instrukcije programa (APLIKACIJE)
6) registre (...)
7) stack (...)
8) heap (...)
9) fajl deskriptore (...)
10) signal-akcije (...)
11) dinamicke biblioteke koje su ucitane (...)
12) i na kraju zna i za sve IPC stvari koje su aktivne u aplikaciji (message queues, pipes, semaphores, shared memory)
Za razliku od svega gore navedenog sto "krasi" proces, "nit" (svaka) radi NEZAVISNO i moze biti sinhronizovana zato jer ima:
1) svoj LICNI stack pointer
2) set registara
3) sve sto treba za "scheduling"
4) grupu "pending" i "blocked" signala
5) grupu svojih licnih podataka
Jedan proces moze imati vise niti, lepa osobina koja niti cini mocnim je da sve te niti DELE resurse (svoje) unutar procesa. Tako da:
1) izmene koje jedna nit napravi na bilo kom deljenom resursu (recimo varijabli) ce biti vidljive U SVIM DRUGIM nitima.
2) dva pokazivaca koji imaju istu vrednost pokazuju na ISTI podatak.
3) citanje/pisanje po istim memorijskim lokacija od strane vise niti je moguce i od programera se zahteva (ako je potrebno) da "odradi" sinhronizaciju.
Na kraju, primer fork()-a (koriscenje procesa):
Code:
#define NFORKS 50000
void do_nothing() {
int i;
i= 0;
}
int main()
{
int pid, j, status;
for (j=0; j<NFORKS; j++) {
/*** provera greske ***/
if ((pid = fork()) < 0 ) {
printf ("fork failed with error code= %d\n", pid);
exit(0);
}
/*** ako je pid nula, onda je u pitanju dete-proces ***/
else if (pid ==0) {
do_nothing();
exit(0);
}
/*** inace je u pitanju roditelj-proces ***/
else {
waitpid(pid, status, 0);
}
}
}
I primer niti:
Code:
#include <pthread.h>
#define NTHREADS 50000
void *do_nothing(void *null) {
int i;
i=0;
pthread_exit(NULL);
}
main() {
int rc, i, j, detachstate;
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (j=0; j<NTHREADS; j++) {
rc = pthread_create(&tid, &attr, do_nothing, NULL);
if (rc) {
printf("ERROR; povratni kod od pthread_create() je %d\n", rc);
exit(-1);
}
/* cekan na nit da zavrsi posao */
rc = pthread_join(tid, NULL);
if (rc) {
printf("ERROR; povratni kod od pthread_join() je %d\n", rc);
exit(-1);
}
}
pthread_attr_destroy(&attr);
pthread_exit(NULL);
}
Za objasnjavanje drugog koda bi trebo jedan podugacak tekst, ovako cu samo reci sta radi pthread_join() - pthread_join() BLOKIRA thread u kome je pozvana (u ovom slucaju MAIN THREAD - main() funkcija je zapravo takozvani MAIN THREAD) dok nit sa ID-jem "tid" ne zavrsi svoj posao.
Druga stvar koju treba primetiti je da umesto ocekivanog exit() ili return() u main() funkciji imamo pthread_exit(NULL); :) Dakle iz MAIN THREAD-a izlazimo kao i iz bilo kojeg drugo, prema POSIX standardu - funkcijom pthread_exit();
E sada malo rezultata... - Gore navedene programe smo iskompajlirali i startovali:
Code:
bash$ gcc proc.c -o proc
bash$ time ./proc
real 0m9.866s
user 0m1.220s
sys 0m8.470s
bash$ gcc nit.c -o nit -lpthread
bash$ time ./nit
real 0m5.878s
user 0m0.590s
sys 0m5.210s
proc.c je prvi listing, nit.c je drugi listing. Rezultati govore sami za sebe.
Dejan Lekic
software engineer, MySQL/PgSQL DBA, sysadmin