Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 06/11/2015, à 16:16

DonutMan75

[RESOLU] Pthread en C : un exemple qui AURAIT DU être simple.... :(

Bonjour,

je travaille actuellement sur le livre de Joëlle Delacroix "Linux, Programmation système et réseau" (Edition Dunod, 4eme édition). Je bloque sur le premier exemple de l'utilisation des threads en C (section 2.2 page 45). Il me semble que le programme soit faux à plusieurs points de vue. J'essaie du coup de le débugguer mais bon... comme c'est la première fois que je découvre les threads, ce n'est pas évident.... Une âme charitable pourrait-elle m'aider ?

Veuillez m'excuser si le message est un peu long. Dans un premier temps, j'essaie de me dépatouiller par moi même et de résoudre les erreurs du programme proposé dans le livre (je ne sais pas si mon analyse est correcte). Dans un second temps, j'essaie de compiler le programme corrigé par mes soins et il ne fonctionne pas comme souhaité..... A minima, pourriez-vous me dire ce qui ne va pas dans mon programme corrigé (en fin de message) ?

Objectif : Le but est de créer 3 threads successifs qui affichent simplement leur identité.

Voici le code original du livre :

1 #include <stdio.h>
2 #include <pthread.h>
3 
4 pthread_t liste[3];
5 
6 void traiter(int i)
7 	{
8 	printf("Je suis le %d-eme thread d'identite %d.%d\n", i, getpid(), pthread_self());
9 	}
10 
11 void main()
12 	{
13 	int i;
14 	for(i=0; i<3; i++)
15 		pthread_create(liste[i], pthread_attr_default, traiter, i);
16 	printf("Je suis le thread initial d'identite %d.%d\n", getpid(), pthread_self());
17	pthread_join();
18	}

A la compilation, mon compilateur préféré d'habitude si courtois, m’inonde d'insultes :

>> >> gcc thread.c -o thread
thread.c: In function 'traiter':
thread.c:8:2: warning: format '%d' expects argument of type 'int', but argument 4 has type 'pthread_t' [-Wformat]
thread.c: In function 'main':
thread.c:15:28: error: 'pthread_attr_default' undeclared (first use in this function)
thread.c:15:28: note: each undeclared identifier is reported only once for each function it appears in
thread.c:15:3: warning: passing argument 1 of 'pthread_create' makes pointer from integer without a cast [enabled by default]
/usr/include/pthread.h:225:12: note: expected 'pthread_t * __restrict__' but argument is of type 'pthread_t'
thread.c:15:3: warning: passing argument 3 of 'pthread_create' from incompatible pointer type [enabled by default]
/usr/include/pthread.h:225:12: note: expected 'void * (*)(void *)' but argument is of type 'void (*)(int)'
thread.c:15:3: warning: passing argument 4 of 'pthread_create' makes pointer from integer without a cast [enabled by default]
/usr/include/pthread.h:225:12: note: expected 'void * __restrict__' but argument is of type 'int'
thread.c:17:2: error: too few arguments to function 'pthread_join'
/usr/include/pthread.h:242:12: note: declared here

Au début je pestais un peu contre la piètre qualité du livre, mais je me suis dit que ça pouvait être un exercice formateur de dépatouiller tout ça. Voici ci-dessous les erreurs que j'ai déjà identifiées. Hélas, je n'ai pas tout résolu et j'aurais besoin de votre aide pour les dernières corrections... Pourriez-vous m'aider à terminer ça ?


[Erreur 1] "thread.c:8:2: warning: format '%d' expects argument of type 'int', but argument 4 has type 'pthread_t' [-Wformat]"

Alors là, il faut que dans les printf je fasse un cast sur le retour de pthread_self(). Autrement dit, remplacer :

printf("Je suis le %d-eme thread d'identite %d.%d\n", i, getpid(), pthread_self());

par :

printf("Je suis le %d-eme thread d'identite %d.%d\n", i, getpid(), (int) pthread_self());


[Erreur 2] "thread.c:15:28: error: 'pthread_attr_default' undeclared (first use in this function)"

Ici j'ai cherché sur le net où était défini "pthread_attr_default" et je n'ai rien trouvé... Néanmoins, j'ai cru comprendre que dans la majorité des cas, il suffisait de mettre NULL comme paramètre donc remplacer :

pthread_create(liste[i], pthread_attr_default, traiter, i);

par

pthread_create(liste[i], NULL, traiter, i);


[Erreur 3] "thread.c:15:3: warning: passing argument 1 of 'pthread_create' makes pointer from integer without a cast [enabled by default]"

Ici c'est je crois assez simple. Il attend un pointeur sur thread donc je dois remplacer

pthread_create(liste[i], NULL, traiter, i);

par

pthread_create(&liste[i], NULL, traiter, i);


[Erreur 4] "thread.c:15:3: warning: passing argument 3 of 'pthread_create' from incompatible pointer type [enabled by default]"
/usr/include/pthread.h:225:12: note: expected 'void * (*)(void *)' but argument is of type 'void (*)(int)'"

Alors là, ça devient un peu chaud pour moi.... Essayons de décortiquer ça...

Il me dit que pour le troisième argument de pthread_create, il s'attendait à recevoir un "void * (*)(void *)" mais qu'à la place il a reçu un "void (*)(int)"...

void * (*)(void *) : c'est un pointeur de fonction qui attend un pointeur générique et renvoie un pointeur générique (???)
void (*)(int) : c'est un pointeur de fonction qui attend un int et renvoie un void (ici c'est donc ma fonction "traiter")

DONC, je suppose que ma fonction traiter n'a pas la bonne forme pour être appelée par pthread_create(). Voici le prototype de pthread_create() :

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

Dans un premier temps, il faudrait donc que je réecrive la fonction "traiter" pour être conforme au type attendu. Après quelques réflexions, je propose :

void* traiter(void *i)
	{
	printf("Je suis le %d-eme thread d'identite %d.%d\n", *(int *)i, getpid(), (int) pthread_self());
	}

Ici nous avons une fonction qui attend un pointeur générique "i" et qui retourne un pointeur générique également.
Au moment de l'affichage de l'itération (la valeur pointée par i donc), il faut d'abord caster i en pointeur sur entier - donc (int *) i - puis accèder à la valeur pointée - donc *(int *) i. Qu'en pensez-vous ? Ca me paraît être correct mais je ne sais absolument pas si c'est la bonne façon de procéder... D'une façon générale, il faut mettre en place toutes les fonctions appelées par pthread_create de cette façon ?

De la même manière, lors de l'appel de la fonction pthread_create, il faut remplacer :

pthread_create(&liste[i], NULL, traiter, i);

par

pthread_create(&liste[i], NULL, traiter, (void *) &i);

Je suppose que ça résous du même coup l'erreur suivante :
"/usr/include/pthread.h:225:12: note: expected 'void * __restrict__' but argument is of type 'int'
thread.c:17:2: error: too few arguments to function 'pthread_join'"



[Erreur 5] "thread.c:17:2: error: too few arguments to function 'pthread_join'"

Le prototype de la fonction pthread_join est le suivant :

 int pthread_join(pthread_t th, void **thread_return);

Si je comprends bien comment cela est sensé se passer :
- les threads naissent et meurent avec la fonction "traiter"
- le processus père peut attendre que les 3 threads s'exécutent tous AVANT de continuer son activité : c'est le rôle de la fonction pthread_join

La meilleure façon de procéder ici serait de mettre en premier argument de pthread_join, l'identifiant du DERNIER thread créé ? En l'occurence, liste[3] donc.

Le second argument (void **thread_return) est un pointeur sur pointeur générique (?) et contiendra (selon le man) la valeur renvoyée par le thread th.
Dans un premier temps, je crois qu'on peut le mettre à NULL si on ne souhaite pas exploiter ça. Je choisis cette option dans un premier temps.

Donc je remplace :

pthread_join();

par

pthread_join(liste[3], NULL);

[CODE CORRIGE]

Au final, voici mon code corrigé :

1 #include <stdio.h>
2 #include <pthread.h>
3 
4 pthread_t liste[3];
5 
6 void* traiter(void *i)
7 	{
8 	printf("Je suis le %d-eme thread d'identite %d.%d\n", *(int *)i, getpid(), (int) pthread_self());
9 	}
10 
11 void main()
12 	{
13 	int i;
14 	for(i=0; i<3; i++)
15 		pthread_create(&liste[i], NULL, traiter, (void *) &i);
15 	printf("Je suis le thread initial d'identite %d.%d\n", getpid(), (int) pthread_self());
16 	pthread_join(liste[3], NULL);
17	}

On compile :

>> gcc thread.c -o thread
/tmp/ccogObSW.o: In function `main':
thread.c:(.text+0x74): undefined reference to `pthread_create'
thread.c:(.text+0xbe): undefined reference to `pthread_join'
collect2: ld returned 1 exit status

Une recherche Google me dit qu'il faut rajouter l'option -pthread avec gcc. Soit.

>> gcc -pthread thread.c -o thread
>>

Hourra, ça semble marcher !
Hélas, quand je lance l'exécutable, j'obtiens le résultat suivant :

>> ./thread
Je suis le thread initial d'identite 20486.-1218996544

1 - L'affichage de la sortie de pthread_self ne semble pas fonctionner... sad
2 - L'appel à la fonction traiter ne fonctionne pas non plus.... sad

J'avoue être très perplexe... il y a des choses que je n'ai fondamentalement pas comprises.... Pourriez-vous m'aider ?
Merci d'avance et encore désolé pour le long mail....

Donut.

Dernière modification par DonutMan75 (Le 09/11/2015, à 11:20)

Hors ligne

#2 Le 06/11/2015, à 17:47

DonutMan75

Re : [RESOLU] Pthread en C : un exemple qui AURAIT DU être simple.... :(

Bonsoir à tous,
après avoir tripatouillé le code un peu dans tous les sens, je crois comprendre ce qu'il se passe avec mon dernier programme....

Tout d'abord, il y a une GROSSE erreur avec la dernière commande : le dernier thread créé c'est liste[2] et non pas liste[3] !
Ensuite, tout va tellement vite que j'ai ajouté des sleep un peu partout et là ça semble mieux se dérouler !!

[CODE CORRIGE]

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_t liste[3];

void* traiter(void *i)
	{
	printf("\t- je suis le %d-eme thread d'identite %d.%d\n", *((int *)i)+1, getpid(), (int) pthread_self());
	sleep(5);
	}
	

void main()
	{	
	
	printf("=== JE SUIS LE PROCESSUS PRINCIPAL D'ID %d ===\n", getpid());
	
	int i;
	for(i=0; i<3; i++)
		{
		pthread_create(&liste[i], NULL, traiter, (void *) &i);
		sleep(1);
		}

	printf("J'attends que mon dernier thread se termine...\n");
	pthread_join(liste[2], NULL);
	}

Ce qui donne à l'exécution :

Tout d'abord :

>> ./thread
=== JE SUIS LE PROCESSUS PRINCIPAL D'ID 30992 ===

Puis toutes les secondes :

>> ./thread
=== JE SUIS LE PROCESSUS PRINCIPAL D'ID 30992 ===
	- je suis le 1-eme thread d'identite 30992.-1219425472
	- je suis le 2-eme thread d'identite 30992.-1227818176
	- je suis le 3-eme thread d'identite 30992.-1236210880

Puis enfin :

>> ./thread
=== JE SUIS LE PROCESSUS PRINCIPAL D'ID 30992 ===
	- je suis le 1-eme thread d'identite 30992.-1219425472
	- je suis le 2-eme thread d'identite 30992.-1227818176
	- je suis le 3-eme thread d'identite 30992.-1236210880
J'attends que mon dernier thread se termine...

Et au bout de 5 secondes, le programme se termine !

Quelques petites remarques, après avoir un peu joué avec tout ça...

Remarque 1 :
il est IDIOT de chercher à connaître le pthread_self() du processus initial non ?

Remarque 2 :
il est MALADROIT d'indexer les threads par i, car c'est une variable COMMUNE à tous les threads.
Autrement dit : si on effectue des opérations coûteuses en temps AVANT d'afficher la valeur de i (par exemple avec un sleep(1) avant le printf() ) alors le programme a le temps de lancer les 3 threads AVANT que ceux-ci affichent i et ils afficheront tous "4" (i.e. la dernière valeur de i en sortie de boucle + 1).

Remarque 3 :
Je n'arrive toujours pas à afficher correctement la valeur de pthread_self() et j'en viens à me demander si c'est vraiment le rôle de cette fonction d'avoir un identifiant du thread "affichable" ? D'une façon générale, comment puis-je contrôler que l'identifiant du thread est correct (par exemple par visualisation avec htop ou ps ?)

Merci à vous smile

Donut.

Dernière modification par DonutMan75 (Le 06/11/2015, à 17:49)

Hors ligne

#3 Le 08/11/2015, à 16:16

claudius01

Re : [RESOLU] Pthread en C : un exemple qui AURAIT DU être simple.... :(

Bonjour,

DonutMan75 a écrit :

Je n'arrive toujours pas à afficher correctement la valeur de pthread_self() et j'en viens à me demander si c'est vraiment le rôle de cette fonction d'avoir un identifiant du thread "affichable" ? D'une façon générale, comment puis-je contrôler que l'identifiant du thread est correct (par exemple par visualisation avec htop ou ps ?)

Peut-être que man pthread_self + man gettid éclaireront tes interrogations avec notamment:
The thread ID returned by pthread_self() is not the same thing as the kernel thread ID returned by a call to gettid(2).

En espérant t'avoir aidé.
À suivre...

Edit: N'ayant pas reussi avec gettid() qui ne semble pas répondre au problème, je te propose d'utiliser syscall(SYS_gettid) comme ci-après:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <syscall.h>

pthread_t liste[3];

void* traiter(void *i)
{
	printf("\t- je suis le %d-eme thread dont le pere est %d: pthread_self() [%u] et de pid [%d]\n",
		*((int *)i)+1, getppid(), (int)pthread_self(), (int)syscall(SYS_gettid));
	sleep(5);

	return NULL;
}
	
int main()
{	
	printf("=== JE SUIS LE PROCESSUS PRINCIPAL D'ID %d ===\n", getpid());

	int i;
	for(i=0; i<3; i++) {
		pthread_create(&liste[i], NULL, traiter, (void *) &i);
		sleep(1);
	}

	printf("J'attends que mon dernier thread se termine...\n");

	for (i=0; i<3; i++) {
		int rtn = pthread_join(liste[i], NULL);
		if (!rtn) {
			printf("Fin du thread de liste[%d] [%u]\n", i, (int)liste[i]);
		}
	}

	printf("Tous les threads sont termines ;-)\n");

	return 0;
}

qui fournit bien le pid du thread du fils lancé; à savoir:

$ ps && ./pthread
  PID TTY          TIME CMD
 6932 pts/2    00:00:00 bash
 9513 pts/2    00:00:00 ps

=== JE SUIS LE PROCESSUS PRINCIPAL D'ID 9514 ===
        - je suis le 1-eme thread dont le pere est 6932: pthread_self() [3084983184] et de pid [9515]
        - je suis le 2-eme thread dont le pere est 6932: pthread_self() [3076590480] et de pid [9516]
        - je suis le 3-eme thread dont le pere est 6932: pthread_self() [3068197776] et de pid [9517]
J'attends que mon dernier thread se termine...
Fin du thread de liste[0] [3084983184]
Fin du thread de liste[1] [3076590480]
Fin du thread de liste[2] [3068197776]
Tous les threads sont termines ;-)

# Dans une autre console et avant la terminaison du 1st thread lancé
$ pstree -p 9514
pthread(9514)-+-{pthread}(9515)
              |-{pthread}(9516)
              `-{pthread}(9517)

Dernière modification par claudius01 (Le 09/11/2015, à 00:48)

Hors ligne

#4 Le 09/11/2015, à 11:19

DonutMan75

Re : [RESOLU] Pthread en C : un exemple qui AURAIT DU être simple.... :(

Bonjour claudius01,
merci pour ta réponse, je ne connaissais ni gettid ni syscall.. Je vais creuser dans ces directions là.

A bientôt smile

D.

Hors ligne

#5 Le 09/11/2015, à 11:36

eiger

Re : [RESOLU] Pthread en C : un exemple qui AURAIT DU être simple.... :(

Une remarque en passant : c'est une mauvaise idée  que de vouloir afficher le résultat de pthread_self(). Cette fonction retourne une variable de type pthread_t, or comme les docs l'indiquent, il faut considérer ceci comme un type opaque. Autrement dit : il n'y a aucune garantie qu'il s'agisse d'un scalaire. Maintenant, sur Linux aujourd'hui, on a de la chance, ce type est effectivement implémenté sous forme d'un scalaire, mais c'est typiquement le genre de détail d'implémentation dont on ne devrait pas tenir compte.
Ce n'est pas par hasard que l'API Pthread contient la fonction "pthread_equal" : elle sert à comparer deux pthread_t de façon portable et sans présupposer du type sous-jacent.
Pour donner un exemple, pthread_t pourrait très bien être implémenté sous forme de structure.

Hors ligne