Erreur à l'exécution : afficher toutes les x secondes y fois un char

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous,

C'est encore moi avec un nouveau problème qui concerne toujours les processus UNIX en C… !

Contexte

Comme l'indique le titre, je dois faire un petit programme qui affiche x fois un certain caractère, avec un délai d'affichage pour ce caractère de y secondes. En fait, ce programme doit faire cela pour plusieurs caractères (si ce n'est pas clair, vous comprendrez en jetant un coup d'oeil à l'exemple qui suit). L'affichage de chaque caractère est à faire en parallèle aux autres affichages (donc le programme est multi-processus).

On passe à ce programme un fichier contenant le caractère à taper, le x et le y. Ce fichier est le suivant :

1
2
a 4 2
b 2 1

Ainsi ce fichier indique au programme d'afficher 'a' 4 fois toutes les 2 secondes, et 'b' 2 fois toutes les 1 seconde, et ce en parallèle.

Le programme est multi-processus : ici, le processus-fils-1 s'occupe du 'a', et le processus-fils-2 s'occupe du 'b'.

Ma question

J'ai écrit un code que vous trouverez en fin de message. Celui-ci compile, et s'exécute sans problème mais ne fait pas ce qu'il est censé faire. En effet, ce programme, en lui passant le fichier que je vous ai donné tout à l'heure, ne fait qu'afficher 'bb' après de plusieurs secondes d'attente. Et par ailleurs, si je mets le délai de 'b' à 1, j'attendrai 1 seconde, à 2, 2 secondes. Mais à 3, j'attendrai 5 secondes, etc. Je trouve ça étrange.

Je ne comprends pas du tout d'où le problème peut venir…

Ma solution

Le bug viendrait du sleep. En effet, sans celui-ci, les processus-enfants sont tous bien créés, et tous les caractères sont affichés (mais ils ne sont pas mélangés puisqu'il n'y a pas de délai). En revanche, avec le sleep, seul le processus du 'b' est lancé et il n'y a pas de délai entre les deux 'b' =/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main(int argc, char* argv[]) {

    pid_t pid;

    FILE* file = fopen(argv[1], "r");
    if(file == NULL) {
        perror("Open");
        exit(EXIT_FAILURE);
    }

    // <!-- The following vars are used for the reading of the file -->
    char current_char = 0;

    char char_to_print; // ie. : the first file's column

    char buffer_number_of_prints[592] = {0}; // ie. : the snd file's column
    int i1 = 0; // Will be used to write in the previous buffer

    char buffer_delay[592] = {0}; // ie. : the thd (and last) file's column
    int i2 = 0;

    int flag_column = 0; // 0 = first file's column, 1 = snd one, 2 = last one (precision : two columns are separated by a space)
    // <!-- ### -->

    while(current_char != EOF) {
        current_char = (char) fgetc(file);

        if(current_char != ' ') { // So current_char is : the char to type XOR The number of times XOR The delay XOR \n
            if(current_char == '\n' || current_char == EOF) { // So we just read an entire line of the file : we have valued the var "char_to_print" + the 2 buffers
                pid = fork(); // A new process is created and will do its job
                if(pid != 0) { // So we just read an entire line + we are the parent process, SO : we re-init all the vars and we're going to read the next file's line
                    flag_column = 0;
                    memset(buffer_number_of_prints, 0, 592);
                    i1 = 0;
                    memset(buffer_delay, 0, 592);
                    i2 = 0;
                    continue; // We're going to read the next file's line

                } else { // If the current process is a child, it has to do its job (ie. : printing the typed char with x delay and y times)
                    int i = 0;
                    int max = atoi(buffer_number_of_prints);
                    int del = atoi(buffer_delay); // These two buffers have been filed by the parent process
                    for(; i < max; i++) {
                        fprintf(stdout, "%i%c ", getpid(), char_to_print);
                        sleep(del);
                    }
                    return 0; // The process ended its job so we stop it

                }
            }

            // If we are the parent process (the child ones have been stopped previously thanks to the instruction `return 0`), we fill the var `char_to_print` and the two buffers according to the value of the var `flag_column`
            if(flag_column == 0) {
                char_to_print = current_char;
            } else if(flag_column == 1) {
                buffer_number_of_prints[i1] = current_char;
                i1++;
            } else if(flag_column == 2) {
                buffer_delay[i2] = current_char;
                i2++;
            }
        }  else { // If we encounter a space in the reading of the file, it means we are changing of column : so we increment this var
            flag_column++;
        }
    }

    // The parent process closes the file's stream
    if(fclose(file) != 0) {
        perror("Close");
        exit(EXIT_FAILURE);
    }

    // Before ending, the parent process wait for its children ones
    wait(NULL);
    return 0;
}

Voilà je vous remercie d'avance et vous souhaite une bonne journée !

+0 -0

Le problème de ton code, c'est que tu mélanges tout dans une seule et même fonction main. Alors ça a beau être commenté, c'est difficile à lire (rajoutons à cela que tu as 6 niveaux d'indentation, et qu'il est nécessaire de défiler vers la droite pour lire les commentaires), et à déboguer.

Tu pourrais séparer dans une autre fonction le traitement des fils, séparer l'analyse du fichier (avec une fonction qui te retournerait les données traitées, qui serait plus facile à relire), etc. Ainsi, tu n'aurais qu'à parser l'entrée, créer tes N fils, et chacun exécuterait ce qu'il a à faire.

Enfin, tu parles de threads, mais je ne vois là que des processus.

Je modifierai le code plus tard (en soi il n'est pas très complexe quand même, après c'est sûr que l'indentation et la longueur des commentaires sont un peu décourageants).

Par contre je viens de remarquer que tout fonctionne bien sans le sleep (mais les affichages ne sont pas mélangés puisqu'il n'y a alors pas de délai) !

Salut,

Bein j'ai pour contrainte de tout faire dans le main. Mais je vais dispatcher le code rien que pour toi !

Lern-X

Un peu d'amabilité ne fait pas de mal. ;)

Ah oui, c'est bien "processus" et non pas "threads". Par contre, quelle est la différence ?

Lern-X

Les threads font partie d'un même processus et partagent une grande partie de leurs ressources de base, alors que les processus ne partagent rien sauf à utiliser des moyens de communications entre processus (mémoire partagée, file de messages, sockets, etc.).

Sinon, tu es certains que tu n'obtiens aucun affichage du premier fils, même quelques temps après ? Parce que pour ma part, cela fonctionne chez moi (Debian Jessie).

Pour ce qui est des problèmes d'affichage, ils sont dû au fait que tu écrits sur le flux stdout qui est temporisé par ligne (en gros, tant qu'un \n n'est pas rencontré, rien n'est écrit) ce qui fait que l'affichage n'a lieu qu'à la fin d'un processus, quand le flux est fermé.

Enfin, note que la fonction wait ne bloque le processus père que jusqu'à la fin d'un de ses fils et non des deux. Du coup, le plus lent des deux processus se retrouve en arrière plan à la mort de son père (et se fait adopter par le processus d'initialisation). Ton terminal autorise-t-il un processus en arrière plan à écrire (tu peux le vérifier à l'aide de la commande stty -a en regardant si tu vois tostop sans « moins » devant) ?

+0 -0

Salut Taurre :) ,

J'ai rajouté récemment la ligne fflush. Désormais j'ai bon affichage, les délais semble respectés et tous les processus enfants semblent être exécutés oui.

L'exécution marche très bien avec par exemple ce fichier :

1
2
a 4 2
b 2 7

Par contre avec celui-ci, un 'b' manque dans l'affichage :

1
2
a 4 2
b 3 7

Concernant le wait : il me semble pourtant qu'un wait(NULL) permet d'attendre la fin de tous les processus-enfants, et non d'un seul.

En effet, "while(wait(NULL)>0) means wait until all child processes exit (or change state) and no more child processes are unwaited-for (or until an error occurs)" - Source : http://stackoverflow.com/questions/13216554/what-does-wait-do-on-unix

On m'a dit de vérifier si le fichier n'est pas dans un format de caractères multi-bytes, mais je ne sais pas ce que ça veut dire…

+0 -0

"while(wait(NULL)>0) means wait until all child processes exit (or change state) and no more child processes are unwaited-for (or until an error occurs)" - Source : http://stackoverflow.com/questions/13216554/what-does-wait-do-on-unix

Note bien la boucle. ;)

On m'a dit de vérifier si le fichier n'est pas dans un format de caractères multi-bytes, mais je ne sais pas ce que ça veut dire…

Lern-X

A priori, ce n'est pas le cas ici vu que tu emplois des caractères ASCII.

Juste pour voir si cela vient du code, celui-ci te donne le même résultat (il manque un « b ») ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <sys/wait.h>
#include <sys/types.h>

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


static void
print(char to_print, unsigned num, unsigned delay)
{
    unsigned i;

    for (i = 0; i < num; ++i) {
        fprintf(stderr, "%c", to_print);
        sleep(delay);
    }

    exit(0);
}


int
main(int argc, char **argv)
{
    char to_print;
    unsigned num;
    unsigned delay;
    FILE *fp;

    if (argv[1] == NULL)
        return EXIT_FAILURE;

    fp = fopen(argv[1], "r");

    if (fp == NULL) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    while (fscanf(fp, " %c %u %u", &to_print, &num, &delay) == 3)
        if (fork() == 0)
            print(to_print, num, delay);

    if (ferror(fp)) {
        perror("fscanf");
        return EXIT_FAILURE;
    }
    if (fclose(fp) != 0) {
        perror("fclose");
        return EXIT_FAILURE;
    }

    while (wait(NULL) > 0)
        ;

    return 0; 
}
+0 -0

Ton code marche, j'ai bien 3 b et 4 a, les délais sont respectés (par contre le processus parent s'arrête 7 secondes après avoir affiché le dernier b).

Je vais voir un peu ce qui est différent entre ton code et le mien ! :)

J'ai essayé de remplacer mon wait(NULL) par la boucle mais ça ne change rien ^^

Edit : Ah si, si je mets while(wait(NULL)>0) { ;;} ça fonctionne ! Par contre, while(wait(NULL)) non (pas d'erreur à la compilation). Je ne comprends pas ?

+0 -0

Ah si, si je mets while(wait(NULL)>0) { ;;} ça fonctionne ! Par contre, while(wait(NULL)) non (pas d'erreur à la compilation). Je ne comprends pas ?

Lern-X

C'est normal, wait ne retourne pas 0 en cas d'erreur.

Voici ce qu'en dit le manuel :

wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.

man 2 wait

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte