This is an old revision of the document!
pthreads reprezintă o bibliotecă din C/C++, nativă Linux, prin care se pot implementa programe multithreaded.
Spre deosebire de OpenMP, pthreads este low-level și oferă o mai mare flexibilitate în ceea ce privește sincronizarea thread-urilor și distribuirea task-urilor către thread-uri.
Pentru a putea folosi pthreads, este necesar să includem în program biblioteca pthread.h
. De asemenea la compilare este necesar să includem flag-ul -lpthread
:
gcc -o program program.c -lpthread ./program
În pthreads, avem un thread principal, pe care rulează funcția main. Din thread-ul principal se pot crea thread-uri noi, care vor executa task-uri în paralel.
Pentru a crea thread-uri în pthreads, folosim funcția pthread_create
:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*thread_function) (void *), void *arg);
unde:
thread
- thread-ul pe care vrem să-l pornimattr
- atributele unui thread (NULL
- atribute default)thread_function
- funcția pe care să o execute thread-ularg
- parametrul trimis la funcția executată de thread (dacă vrem să trimitem mai mulți parametri, îi împachetăm într-un structExemplu de funcție pe care o execută un thread:
void *f(void *arg) { // do stuff // aici putem să întoarcem un rezultat, dacă este cazul pthread_exit(NULL); // termină un thread - mereu apelat la finalul unei funcții executate de thread, dacă nu întoarcem un rezultat în funcție }
Pentru terminarea thread-urilor, care vor fi “lipite înapoi” în thread-ul principal, folosim funcția pthread_join
, care așteaptă terminarea thread-urilor:
int pthread_join(pthread_t thread, void **retval);
unde:
thread
- thread-ul pe care îl așteptăm să termineretval
- valoarea de retur a funcției executate de thread (poate fi NULL
)Exemplu de program scris folosind pthreads:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 2 void *f(void *arg) { long id = *(long*) arg; printf("Hello World din thread-ul %ld!\n", id); return NULL; } int main(int argc, char *argv[]) { pthread_t threads[NUM_THREADS]; int r; long id; void *status; long arguments[NUM_THREADS]; for (id = 0; id < NUM_THREADS; id++) { arguments[id] = id; r = pthread_create(&threads[id], NULL, f, (void *) &arguments[id]); if (r) { printf("Eroare la crearea thread-ului %ld\n", id); exit(-1); } } for (id = 0; id < NUM_THREADS; id++) { r = pthread_join(threads[id], &status); if (r) { printf("Eroare la asteptarea thread-ului %ld\n", id); exit(-1); } } return 0; }
În caz că dorim să trimitem mai mulți parametri funcției executate de threads, folosim un struct
, în care incapsulăm datele, și îl trimitem ca parametru al funcției executate de threads.
Exemplu:
Când dorim să paralelizăm operații efectuate pe arrays, fiecărui thread îi va reveni o bucată din array, pe acea bucată executând funcția atribuită lui (thread-ului).
Formula de împărțire:
start_index = id * (double) n / p end_index = min(n, (id + 1) * (double) n / p)), unde id = id-ul thread-ului, n = dimensiunea array-ului, p = numărul de threads
Un mutex (mutual exclusion) este folosit pentru a delimita și pentru a proteja o zonă critică, unde au loc, de regulă, operații de citire și de scriere. Un singur thread intră în zona critică (o rezervă pentru el - lock), unde se execută instrucțiuni, iar celelalte thread-uri așteaptă ca thread-ul curent să termine de executat instrucțiunile din zona critică. După ce thread-ul curent termină de executat instrucțiuni în zona critică, aceasta o eliberează (unlock) și următorul thread urmează aceiași pași.
Pentru zonele critice în pthreads folosim pthread_mutex_t
, care reprezintă un mutex, care asigură faptul că un singur thread accesează zona critică la un moment dat, thread-ul deținând lock-ul pe zona critică în momentul respectiv, și că celelalte thread-uri care nu au intrat încă în zona critică așteaptă eliberarea lock-ului de către thread-ul aflat în zona critică în acel moment.
Funcții pentru mutex:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
Exemplu de folosire:
Barieră
Bariera este folosită atunci când dorim să sincronizăm thread-urile încât să ajungă (să se sincronizeze) în același punct. Mai concret, ea asigură faptul că niciun thread, gestionat de barieră, nu trece mai departe de zona în care aceasta este amplasată decât atunci când toate thread-urile gestionate de barieră ajung în aceeași zonă. În pthreads folosim structura pthread_barrier_t
pentru barieră.
Funcții:
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned count);
int pthread_barrier_wait(pthread_barrier_t *barrier);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
Exemplu de folosire: