Le multi thread

La fonction pthread_create()

Sous Unix, chaque processus comporte un thread principal et on peut y lancer d'autres threads, grâce à pthread_create(), qui exécuteront chacun le code d'une fonction passée en paramètre.

Cette fonction pthread_create() prend 4 paramètres :

La fonction retourne la valeur 0 si tout s'est bien passé et une valeur non nulle si il y a eu un problème dans la création du thread.

#include <stdio.h>
#include <pthread.h>		// pour la fonction pthread_create

int a=10;

void* thread_function(void* arg) {
    printf("Le thread secondaire : a vaut au départ %d\n",a);
	a=20;
	sleep(1);
	printf("Le thread secondaire : a vaut ensuite %d\n",a);
	sleep(1);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int status;

    status = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (status != 0) {
        printf("erreur de création du thread");
		return(1);
    }
	printf("Le thread principal : a vaut au départ %d\n",a);
	a=30;
	sleep(1);
	printf("Le thread principal : a vaut ensuite %d\n",a);
	sleep(1);
	return(0);
}
	

Remarque : A l'édition de liens, il convient de spécifier l'usage de la bibliothèque pthread. Par exemple, on pourra compiler le programme thread1.c via la commande suivante :

gcc thread1.c -o thread1 -lpthread

Le partage des sockets

Quand on crée un thread, on partage, entre les deux tâches s'exécutant en parallèle, le code et les données du processus. Un seul processus gérant donc en parallèle tous les sockets, on sera limité par le nombre d'entrées qu'un processus peut avoir dans sa table des descripteurs de fichiers.

Exemple

Voici l'exemple d'un serveur qui renvoie, en echo, ce qu'il reçoit du client.

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// pour la fonction bzero
#include <arpa/inet.h>	// pour la fonction inet_addr

#include <pthread.h>		// pour la fonction pthread_create


void *communication_serveur(void *arg) {
	int fd;
	int len;
	char buf[100];

	fd= *((int*) arg);
	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);

	shutdown(fd, SHUT_RDWR);
	close(fd);
	printf("Client déconnecté par le serveur !\n");
	return NULL;
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;
	pthread_t thread_id;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Erreur de creation de la socket !\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Adresse IPv4 invalide !\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Erreur d'accès au port serveur !\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Erreur de création de la file d'attente !\n");
		exit(1);
	}
	printf("Serveur lancé !\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connexion acceptée !\n");
			err = pthread_create(&thread_id, NULL, communication_serveur, &fd_con);

		}
	}
	return 0;
}
	

Eviter une fuite de la mémoire

Avec le code ci-dessus à chaque nouvelle connexion, on crée un thread qui se termine ensuite mais n'est pas détruit automatiquement après usage. On peut donc constater, en consultant le fichier /proc/pid/statuspid est le numéro de processus du serveur, l'augmentation progressive de la mémoire utilisé par le processus.

Pour éviter, cette fuite de mémoire, en libérant automatiquement la mémoire utilisée par le thread quand il se termine, on ajoute après l'instruction err = pthread_create(...); l'instruction pthread_detach(thread_id); (à la ligne 68).

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>

#include <string.h>		// pour la fonction bzero
#include <arpa/inet.h>	// pour la fonction inet_addr

#include <pthread.h>		// pour la fonction pthread_create


void *communication_serveur(void *arg) {
	int fd;
	int len;
	char buf[100];

	fd= *((int*) arg);
	while ((len=read(fd,&buf,sizeof(buf)))!=0)
		write(fd,&buf,len);

	shutdown(fd, SHUT_RDWR);
	close(fd);
	printf("Client déconnecté par le serveur !\n");
	return NULL;
}

int main()
{
	int fd_srv, fd_con;
	struct sockaddr_in	adr_srv, adr_cli;
	int	err;
	socklen_t adr_cli_len;
	pthread_t thread_id;

	fd_srv = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (fd_srv<0) {
		printf("Erreur de creation de la socket !\n");
		exit(1);
	}

	bzero(&adr_srv, sizeof(adr_srv));
	adr_srv.sin_family = AF_INET;
	err = inet_aton("127.0.0.1", &adr_srv.sin_addr);
	if (err == 0) {
		printf("Adresse IPv4 invalide !\n");
		exit(1);
	}
	adr_srv.sin_port = htons(8080);
	err = bind(fd_srv, (struct sockaddr *) &adr_srv, sizeof(adr_srv));
	if (err != 0) {
		printf("Erreur d'accès au port serveur !\n");
		exit(1);
	}
	err = listen(fd_srv, 5);
	if (err != 0) {
		printf("Erreur de création de la file d'attente !\n");
		exit(1);
	}
	printf("Serveur lancé !\n");
	while(1) {
		adr_cli_len = sizeof(adr_cli);
		fd_con = accept(fd_srv, (struct sockaddr *) &adr_cli, &adr_cli_len);
		if (fd_con>=0) {
			printf("Connexion acceptée !\n");
			err = pthread_create(&thread_id, NULL, communication_serveur, &fd_con);
			pthread_detach(thread_id);
		}
	}
	return 0;
}