Programmation d'application dans un environnement Linux (4): programmation multithread

Un: concept de fil

       Thread, la plus petite unité que le système d'exploitation peut planifier. Un processus UNIX typique peut être vu comme un seul thread de contrôle: un processus ne peut faire qu'une seule chose à un certain moment, et avec plusieurs threads de contrôle, chaque thread peut gérer ses propres tâches indépendamment à un certain moment. Cette méthode présente de nombreux avantages:

  • En attribuant des threads de traitement séparés pour chaque type d'événement, le code de gestion des événements asynchrones peut être simplifié. Chaque thread peut utiliser le mode de programmation synchrone lors du traitement d'événements Le mode de programmation synchrone est beaucoup plus simple que le mode de programmation asynchrone.
  • La différence avec le processus est que plusieurs threads peuvent accéder automatiquement au même espace d'adressage de stockage et aux mêmes descripteurs de fichier.
  • Certains problèmes peuvent être décomposés en exécution multi-thread pour améliorer le débit de l'ensemble du programme.
  • Les programmes interactifs peuvent également améliorer le temps de réponse en utilisant plusieurs threads.

Deux: API de thread

1. ID du fil

L'ID de thread est représenté par le type de données pthread_t. Dans le thread, vous pouvez obtenir son propre ID de thread en appelant la fonction pthread_self

#include <pthread.h>

pthread_t pthread_self(void);

2. Création de threads

#include <pthread.h>

int pthread_create(pthread_t *thread, 
                    const pthread_attr_t *attr,
                    void *(*start_routine) (void *), 
                    void *arg);
返回值:成功,返回0;否则返回错误编号

3. Terminaison de fil

Un seul thread peut sortir de 3 manières:

  • Le thread peut simplement revenir de la routine de démarrage et la valeur de retour est le code de sortie du thread.
  • Un thread peut être annulé par d'autres threads dans le même processus.
  • Thread appelle pthread_exit

Cette fonction est une fonction de sortie de thread. En quittant, vous pouvez transmettre un type de données void * au thread principal. Si vous choisissez de ne pas envoyer de données, vous pouvez remplir le paramètre avec NULL.

Les autres threads du processus peuvent également accéder à ce pointeur en appelant la fonction pthread_join.

#include <pthread.h>
void pthread_exit(void *retval);

 

Cette fonction est une fonction de récupération de thread et l'état par défaut est l'état de blocage, jusqu'à ce que le thread soit correctement récupéré et bloqué. Le premier paramètre est le numéro TID du thread à recycler et le second paramètre est les données envoyées par le thread après le recyclage du thread.

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
成功:返回0

Cette fonction est une fonction de recyclage en mode non bloquant. Elle juge s'il faut recycler le thread par la valeur de retour. Si le recyclage réussit, elle renvoie 0. Les paramètres restants sont cohérents avec pthread_join.

#include <pthread.h>
int pthread_tryjoin_np(pthread_t thread, void **retval);
成功:返回0

Un thread peut également demander l'annulation d'autres threads dans le même processus en appelant la fonction thread_cancel.

#include <pthread.h>
int pthread_cancel(pthread_t thread);
成功:返回0

4. Exemple

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

void *fun1(void *arg)
{
	printf("Pthread:1 come!\n");
	while(1){
		sleep(1);
	}
}

void *fun2(void *arg)
{
	printf("Pthread:2 come!\n");
	pthread_cancel((pthread_t )(long)arg);
	pthread_exit(NULL);
}

int main()
{
	int ret,i,flag = 0;
	void *Tmp = NULL;
	pthread_t tid[2];
	ret = pthread_create(&tid[0],NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	sleep(1);
	ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	while(1){
		for(i = 0;i <2;i++){
			if(pthread_tryjoin_np(tid[i],NULL) == 0){
				printf("Pthread : %d exit !\n",i+1);
				flag++;	
			}
		}
		if(flag >= 2) break;
	}
	return 0;
}

Trois: contrôle de la synchronisation des threads

       Le multithreading pose le problème de la concurrence pour les ressources critiques. Pour résoudre ce problème, les threads introduisent des verrous d'exclusion mutuelle pour résoudre l'accès aux ressources critiques. La protection des ressources critiques en verrouillant les ressources ne peut être effectuée que par un seul thread. Une fois l'opération terminée, les threads restants peuvent obtenir le droit de fonctionner.

1. Initialisez le mutex

#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
成功:返回0

Pour initialiser un mutex, vous pouvez également appeler une macro pour initialiser rapidement:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

2. Détruisez le mutex

#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
成功:返回0

3. Verrouillage / déverrouillage Mutex

Si le mutex est verrouillé, le thread appelant se bloquera jusqu'à ce que le mutex soit déverrouillé.

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

Si le thread ne souhaite pas être bloqué, vous pouvez utiliser la fonction suivante pour essayer de verrouiller le mutex.

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功:返回0

4. Exemple

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

pthread_mutex_t mutex;

int Num = 0;

void *fun1(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num < 3){
		Num++;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	pthread_mutex_lock(&mutex);
	while(Num > -3){
		Num--;
		printf("%s:Num = %d\n",__FUNCTION__,Num);
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2;
	ret = pthread_mutex_init(&mutex,NULL);
	if(ret != 0){
		perror("pthread_mutex_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

Quatre: contrôle de l'ordre d'exécution des threads

       Les verrous Mutex résolvent le problème de l'accès aux ressources critiques des threads, mais le problème de l'ordre d'exécution des threads n'a pas été résolu, donc le concept de sémaphore est introduit et l'ordre d'exécution des threads est contrôlé par le sémaphore.

1. Initialisation du sémaphore

Cette fonction peut initialiser un sémaphore, le premier paramètre est passé dans l'adresse de type sem_t, le deuxième paramètre est passé en 0 pour représenter le contrôle du thread, sinon c'est le contrôle du processus, le troisième paramètre représente la valeur initiale du sémaphore, et 0 représente le blocage.1 signifie courir. Une fois le sémaphore initialisé, il retournera 0 si l'exécution réussit.

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
成功:返回0

2. Fonctionnement du sémaphore P / V (blocage)

          La fonction de sem_wait est de détecter si le sémaphore spécifié a des ressources disponibles. Si aucune ressource n'est disponible, il bloquera l'attente. Si des ressources sont disponibles, il exécutera automatiquement l'opération "sem-1". Le soi-disant "sem-1" est cohérent avec la valeur du troisième paramètre dans la fonction d'initialisation ci-dessus, et il renverra 0 s'il est exécuté avec succès.

          La fonction sem_post libérera les ressources du sémaphore spécifié et exécutera l'opération "sem + 1".

Grâce aux deux fonctions ci-dessus, l'opération dite PV peut être terminée, c'est-à-dire l'application et la libération du sémaphore, et le contrôle de la séquence d'exécution du thread est terminé.

#include <pthread.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
成功:返回0

 

Fonction de ressource d'application de sémaphore non bloquante:

#include <pthread.h>
int sem_trywait(sem_t *sem);
成功:返回0

3. Destruction du sémaphore

#include <pthread.h>
int sem_destory(sem_t *sem);
成功:返回0

4. Exemple

#define _GNU_SOURCE 
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <semaphore.h>

sem_t sem1,sem2,sem3;//申请的三个信号量变量

void *fun1(void *arg)
{
	sem_wait(&sem1);//因sem1本身有资源,所以不被阻塞 获取后sem1-1 下次会会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem2);// 使得sem2获取到资源
	pthread_exit(NULL);
}

void *fun2(void *arg)
{
	sem_wait(&sem2);//因sem2在初始化时无资源会被阻塞,直至14行代码执行 不被阻塞 sem2-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem3);// 使得sem3获取到资源
	pthread_exit(NULL);
}

void *fun3(void *arg)
{
	sem_wait(&sem3);//因sem3在初始化时无资源会被阻塞,直至22行代码执行 不被阻塞 sem3-1 下次会阻塞
	printf("%s:Pthread Come!\n",__FUNCTION__);
	sem_post(&sem1);// 使得sem1获取到资源
	pthread_exit(NULL);
}

int main()
{
	int ret;
	pthread_t tid1,tid2,tid3;
	ret = sem_init(&sem1,0,1);  //初始化信号量1 并且赋予其资源
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem2,0,0); //初始化信号量2 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = sem_init(&sem3,0,0); //初始化信号3 让其阻塞
	if(ret < 0){
		perror("sem_init");
		return -1;
	}
	ret = pthread_create(&tid1,NULL,fun1,NULL);//创建线程1
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2,NULL,fun2,NULL);//创建线程2
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid3,NULL,fun3,NULL);//创建线程3
	if(ret != 0){
		perror("pthread_create");
		return -1;
	}
	/*回收线程资源*/
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);

	/*销毁信号量*/
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	sem_destroy(&sem3);

	return 0;
}

Cinq: attributs de fil

Le deuxième paramètre (pthread_attr_t * attr) de la fonction pthread_create () représente les attributs du thread. Dans l'exemple précédent, la valeur est définie sur NULL, c'est-à-dire que les attributs par défaut sont utilisés et que plusieurs attributs du thread peuvent être modifiés. Ces attributs incluent principalement les attributs de liaison, les attributs de séparation, l'adresse de pile, la taille de pile et la priorité. Les attributs par défaut du système sont sans liaison, sans séparation, une pile par défaut de 1M et la même priorité que le processus parent. Ce qui suit explique d'abord les concepts de base d'attributs de liaison et de séparation des attributs.

  • Attributs de liaison.

Comme mentionné précédemment, Linux utilise un mécanisme de threading "un-à-un", c'est-à-dire qu'un thread utilisateur correspond à un thread du noyau. L'attribut de liaison signifie qu'un thread utilisateur est affecté de manière fixe à un thread du noyau, car la planification des tranches de temps CPU est orientée vers les threads du noyau (c'est-à-dire des processus légers), de sorte que les threads avec des attributs de liaison peuvent garantir le total Il y a un thread du noyau correspondant à lui. L'attribut non lié correspondant signifie que la relation entre les threads utilisateur et les threads du noyau n'est pas toujours fixe, mais est contrôlée par le système.

  • Attributs de liaison.

Comme mentionné précédemment, Linux utilise un mécanisme de threading "un-à-un", c'est-à-dire qu'un thread utilisateur correspond à un thread du noyau. L'attribut de liaison signifie qu'un thread utilisateur est affecté de manière fixe à un thread du noyau, car la planification des tranches de temps CPU est orientée vers les threads du noyau (c'est-à-dire des processus légers), de sorte que les threads avec des attributs de liaison peuvent garantir le total Il y a un thread du noyau correspondant à lui. L'attribut non lié correspondant signifie que la relation entre les threads utilisateur et les threads du noyau n'est pas toujours fixe, mais est contrôlée par le système.

Le paramétrage de ces attributs se fait via des fonctions spécifiques. Habituellement, la fonction pthread_attr_init () est d'abord appelée pour initialiser, puis la fonction de paramétrage d'attribut correspondante est appelée, et enfin la fonction pthread_attr_destroy () est appelée pour nettoyer et récupérer la structure d'attribut assignée aiguille. La fonction pour définir l'attribut de liaison est pthread_attr_setscope (), la fonction pour définir l'attribut de détachement de thread est pthread_attr_setdetachstate (), et les fonctions associées pour définir la priorité du thread sont pthread_attr_getschedparam () (obtenir la priorité du thread) et pthread_attr_setschedparam () (set) priorité). Après avoir défini ces propriétés, vous pouvez appeler la fonction pthread_create () pour créer un thread.

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr)
attr:线程属性结构指针
返回值: 成功0、 错误返回错误码


int pthread_attr_setscope(pthread_attr_t *attr, int scope)
attr:线程属性结构指针
scope:{
    PTHREAD_SCOPE_SYSTEM:绑定
    PTHREAD_SCOPE_PROCESS:非绑定 
}
返回值:成功0、错误-1


int pthread_attr_setscope(pthread_attr_t *attr, int detachstate)
attr:线程属性
detachstate:{
    PTHREAD_CREATE_DETACHED:分离
    PTHREAD _CREATE_JOINABLE:非分离
}
返回值:成功0、错误返回错误码

int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功:0、错误返回错误码

int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param)
attr:线程属性结构指针
param:线程优先级
返回值:成功0、错误返回错误码

Exemple: pour éviter toute complexité inutile, un thread est créé ici. Ce thread a des propriétés de liaison et de détachement, et le thread principal obtient la
fin du thread via une variable indicateur finish_flag , au lieu d'appeler la fonction pthread_join ().

/*thread_attr.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define REPEAT_NUMBER 3 /* 线程中的小任务数 */
#define DELAY_TIME_LEVELS 10.0 /* 小任务之间的最大时间间隔 */
int finish_flag = 0;

void *thrd_func(void *arg)
{
    int delay_time = 0;
    int count = 0;
    printf("Thread is starting\n");
    for (count = 0; count < REPEAT_NUMBER; count++)
    {
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
        sleep(delay_time);
        printf("\tThread : job %d delay = %d\n", count, delay_time);
    }
    printf("Thread finished\n");
    finish_flag = 1;
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t thread;
    pthread_attr_t attr;
    int no = 0, res;
    void * thrd_ret;
    srand(time(NULL));
    /* 初始化线程属性对象 */
    res = pthread_attr_init(&attr);
    if (res != 0)
    {
        printf("Create attribute failed\n");
        exit(res);
    }
    /* 设置线程绑定属性 */
    res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    /* 设置线程分离属性 */
    res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (res != 0)
    {
        printf("Setting attribute failed\n");
        exit(res);
    }
    res = pthread_create(&thread, &attr, thrd_func, NULL);
    if (res != 0)
    {
        printf("Create thread failed\n");
        exit(res);
    }
    /* 释放线程属性对象 */
    pthread_attr_destroy(&attr);
    printf("Create tread success\n");
    while(!finish_flag)
    {
        printf("Waiting for thread to finish...\n");
        sleep(2);
    }
    return 0;
}

 

Je suppose que tu aimes

Origine blog.csdn.net/qq_34968572/article/details/107490102
conseillé
Classement