Gestion des IO bloquants et non bloquants

En parlant d'IO, les concepts de bloqueur/non-bloqueur, asynchrone/synchrone sont incontournables. Honnêtement, après avoir consulté de nombreuses ressources en ligne, une explication couramment acceptée est que : "Lors de l'utilisation d'IO, à la fois bloquant et non bloquant relèvent de l'IO synchroen, tandis que l'IO asynchrone utilise des API spéciales". Une réponse sur Zhihu semble assez fiable pour servir de point de départ :

http://www.zhihu.com/question/19732473

Bien que ces ressources fournissent des explications théoriques, je préfère approfondir ces concepts par des exemples pratiques afin de mieux comprendre la relation entre blocage/non-blocage et synchronisation/asynchronisme.

Bloquant et Non Bloquant

Ces termes désignent deux stratégies utilisées par le système d'exploitation (OS) lorsqu'un processus tente de lire des données qui ne sont pas encore prêtes. Dans ce contexte, le sujet principal est l'OS et l'objet est le processus, ce qui signifie que la question du blocage ou non concerne directement le processus. Lorsque le processus demande des données au matériel via l'OS, deux approches sont possibles :

  1. Bloquant : Le processus est mis en pause dans une file d'attente jusqu'à ce que les données soient disponibles.
  2. Non bloquant : Une indication immédiate est renvoyée au processus pour signaler que les données ne sont pas encore prêtes.

Voici un exemple simple où un programme lit continuellement des données depuis l'entrée standard et les affiche :

int main() {
    char buffer[1024];
    int nbRead;
    
    while(1) {
        nbRead = read(0, buffer, sizeof(buffer));
        printf("Nombre de caractères lus : %d\n", nbRead);
        sleep(2);
    }
    return 0;
}

Par défaut, les opérations d'entrée/sortie sont bloquantes. Par conséquent, lorsque vous exécutez ce programme, il restera en attente jusqu'à ce qu'une entrée soit fournie :

utilisateur@machine:~$ ./programme_bloquant

Pour transformer cette opération en mode non bloquant, on peut utiliser la fonction fcntl pour modifier les attributs du descripteur de fichier stdin :

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

int main() {
    int flags, error;
    char buffer[1024];
    ssize_t nbRead;

    // Rendre stdin non bloquant
    flags = fcntl(0, F_GETFL, 0);
    flags |= O_NONBLOCK;
    error = fcntl(0, F_SETFL, flags);

    if (error < 0) {
        perror("Échec du changement vers non-bloquant");
        return 1;
    }

    while(1) {
        nbRead = read(0, buffer, sizeof(buffer));
        if (nbRead == -1 && errno == EAGAIN) {
            printf("Aucune donnée disponible.\n");
        } else {
            printf("Données reçues : %zd octets\n", nbRead);
        }
        sleep(2);
    }
    return 0;
}

Dans ce cas, si aucune donnée n'est disponible, le programme ne reste pas bloqué mais retourne -1 avec errno défini à EAGAIN, indiquant qu'aucune donnée n'est présente pour le moment. Voici un exemple de sortie :

utilisateur@machine:~$ ./programme_non_bloquant
Aucune donnée disponible.
Aucune donnée disponible.
Aucune donnée disponible.
...

Ainsi, nous voyons clairement que :

  • En mode bloquant, le programme s'arrête jusqu'à ce que des données soient disponibles.
  • En mode non bloquant, le programme continue son exécution tout en recevant des notifications appropriées sur l'état des données.

Étiquettes: Linux fcntl IO

Publié le 23 octobre à 02h43