Cet article va utiliser deux exemples pour illustrer l’utilisation pratique de pthread pour la programmation multithread, principalement en deux parties :
Calcul parallèle de la valeur de PI par division des données
Développement d’un pool de threads basé sur le modèle producteur-consommateur, où la logique de traitement des affaires sera simplifiée pour se concentrer sur la gestion et la synchronisation des threads
1 Calcul de Pi
1.1 Brève description de l’idée
Selon la formule de Leibniz, en effectuant un plus grand nombre de calculs multithread, on s’approche de $\pi$. La méthode multithread est utilisée pour diviser les données, chaque thread prenant en charge une partie des données pour accélérer le processus. Étant donné que l’accès multithread au résultat global peut entraîner des conflits, des mutex et des sémaphores sont utilisés pour organiser les threads afin qu’ils ajoutent de manière ordonnée les résultats locaux au résultat global.
#include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<math.h> constintBLOCK_SIZE=100000;doublesum=0;intnum_threads;// Définir le mutex et la variable de condition
pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;pthread_cond_tcond=PTHREAD_COND_INITIALIZER;void*calculate_block(void*thread_id){longid=(long)thread_id;intstart=id*BLOCK_SIZE;intend=start+BLOCK_SIZE;doubleblock_sum=0;for(inti=start;i<end;i++){doubleterm=pow(-1,i)/(2*i+1);block_sum+=term;}pthread_mutex_lock(&mutex);sum+=block_sum;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}intmain(){num_threads=8;pthread_tthreads[num_threads];for(longi=0;i<num_threads;i++)pthread_create(&threads[i],NULL,calculate_block,(void*)i);for(inti=0;i<num_threads;i++)pthread_join(threads[i],NULL);printf("%lf",sum*4);}
2 Conception du pool de threads
2.1 Implémentation du pool de threads
Utiliser une file d’attente de tâches comme tampon entre le producteur et le consommateur, chaque élément de la file d’attente de tâches contenant la fonction à exécuter et les paramètres de la fonction, le code correspondant est le suivant :
Le pool de threads contient une file d’attente de tâches, plusieurs threads, des mutex et des sémaphores ainsi que d’autres attributs clés, définis comme suit :
Du côté du producteur, la fonction thread_pool_enqueue est utilisée pour ajouter des tâches à la file d’attente de tâches. Lorsqu’un producteur produit une tâche, il utilise d’abord le mutex pool->mutex du pool de threads pour protéger la file d’attente de tâches, empêchant plusieurs threads de modifier simultanément la file d’attente de tâches, puis utilise la variable de condition pool->cond pour notifier le consommateur de l’arrivée d’une nouvelle tâche.
Du côté du consommateur, la fonction thread_pool_worker est utilisée pour extraire les tâches de la file d’attente de tâches et les exécuter. Lorsque le consommateur extrait une tâche de la file d’attente de tâches, il utilise le mutex pool->mutex pour protéger la file d’attente de tâches. Si la file d’attente de tâches est vide, le consommateur sera bloqué par la variable de condition pool->mutex, en attendant que le producteur ajoute de nouvelles tâches à la file d’attente de tâches.
Le démarrage du pool de threads comprend l’initialisation de la file d’attente de tâches, des sémaphores, le démarrage des threads consommateurs ; la fermeture du pool de threads consiste principalement à attendre la fin de l’exécution de tous les threads ; les deux sont implémentés comme suit :