web-dev-qa-db-fra.com

Comment utiliser printf () dans plusieurs threads

J'implémente un programme multithread qui utilise différents cœurs et de nombreux threads sont exécutés simultanément. Chaque thread effectue un appel à printf() et le résultat n'est pas lisible.

Comment puis-je rendre printf() atomique, afin qu'un appel printf() dans un thread ne soit pas en conflit avec un appel printf() dans un autre?

17
user3242743

Spécifications POSIX

La spécification POSIX comprend ces fonctions:

Versions des fonctions getc(), getchar(), putc() et putchar() respectivement nommées getc_unlocked(), getchar_unlocked(), putc_unlocked() et putchar_unlocked() doivent être fournis qui sont fonctionnellement équivalents aux versions originales, à l'exception qu'ils ne doivent pas être implémentés de manière entièrement thread-safe. Ils doivent être thread-safe lorsqu'ils sont utilisés dans une portée protégée par flockfile() (ou ftrylockfile()) et funlockfile(). Ces fonctions peuvent être utilisées en toute sécurité dans un programme multithread si et seulement si elles sont appelées alors que le thread appelant possède l'objet (FILE *), Comme c'est le cas après un appel réussi à flockfile() ou ftrylockfile().

Les spécifications de ces fonctions mentionnent:

La spécification pour flockfile() et al inclut l'exigence de couverture:

Toutes les fonctions qui référencent des objets (FILE *), À l'exception de celles dont le nom se termine par _unlocked, Doivent se comporter comme si elles utilisaient flockfile() et funlockfile() en interne pour obtenir la propriété de ces (FILE *) objets.

Cela remplace le code suggéré dans les éditions précédentes de cette réponse. La norme POSIX spécifie également:

Les fonctions [*lockfile()] doivent se comporter comme si un nombre de verrous était associé à chaque objet (FILE *). Ce nombre est implicitement initialisé à zéro lorsque l'objet (FILE *) Est créé. L'objet (FILE *) Est déverrouillé lorsque le nombre est nul. Lorsque le nombre est positif, un seul thread possède l'objet (FILE *). Lorsque la fonction flockfile() est appelée, si le nombre est nul ou si le nombre est positif et que l'appelant possède l'objet (FILE *), Le nombre doit être incrémenté. Sinon, le thread appelant doit être suspendu, en attendant que le décompte revienne à zéro. Chaque appel à funlockfile() doit décrémenter le nombre. Cela permet de faire correspondre les appels à flockfile() (ou les appels réussis à ftrylockfile()) et à funlockfile().

Il existe également les spécifications des fonctions d'E/S de caractères:

Les fonctions de sortie formatées sont documentées ici:

Une disposition clé de la spécification printf() est:

Les caractères générés par fprintf() et printf() sont imprimés comme si fputc() avait été appelé.

Notez l'utilisation de 'comme si' . Cependant, chacune des fonctions printf() est nécessaire pour appliquer le verrou afin que l'accès à un flux soit contrôlé dans une application multithread. Un seul thread à la fois peut utiliser un flux de fichiers donné. Si les opérations sont des appels de niveau utilisateur vers fputc(), alors d'autres threads peuvent traverser la sortie. Si les opérations sont des appels de niveau utilisateur tels que printf(), alors l'appel entier et tous les accès au flux de fichiers sont efficacement protégés de sorte qu'un seul thread l'utilise jusqu'à ce que l'appel à printf() retourne .

Dans la section de la section System Interfaces: General Information de POSIX au sujet de Threads , il est écrit:

2.9.1 Sécurité des fils

Toutes les fonctions définies par ce volume de POSIX.1-2008 doivent être thread-safe, sauf que les fonctions suivantes1 n'ont pas besoin d'être thread-safe.

… une liste de fonctions qui n'ont pas besoin d'être thread-safe…

… Les fonctions getc_unlocked(), getchar_unlocked(), putc_unlocked() et putchar_unlocked() n'ont pas besoin d'être thread-safe sauf si le thread appelant possède le (FILE * ) accessible par l'appel, comme c'est le cas après un appel réussi aux fonctions flockfile() ou ftrylockfile().

Les implémentations doivent fournir une synchronisation interne si nécessaire afin de satisfaire cette exigence.

La liste des fonctions exemptées ne contient pas fputc ou putc ou putchar (ou printf() et al).

Interprétation

Réécrit le 26/07/2017.

  1. La sortie au niveau des caractères sur un flux est thread-safe sauf si vous utilisez les fonctions "déverrouillées" sans verrouiller d'abord le fichier.
  2. Les fonctions de niveau supérieur telles que printf() appellent conceptuellement flockfile() au début et funlockfile() à la fin, ce qui signifie que les fonctions de sortie de flux définies par POSIX sont également thread-safe par appel.
  3. Si vous souhaitez regrouper des opérations sur un flux de fichiers pour un seul thread, vous pouvez le faire en utilisant explicitement des appels à flockfile() et funlockfile() sur le flux pertinent (sans interférer avec l'utilisation par le système de la Fonctions *lockfile().

Cela signifie qu'il n'est pas nécessaire de créer des mutex ou des mécanismes équivalents pour vous-même; l'implémentation fournit les fonctions pour vous permettre de contrôler l'accès à printf() et al dans une application multi-thread.

… Le code de la réponse précédente a été supprimé car il n'est plus pertinent…

19
Jonathan Leffler

Afin de ne pas mélanger les sorties de différents threads, vous devez vous assurer qu'un seul thread utilise printf à la fois. Pour ce faire, la solution la plus simple consiste à utiliser un mutex. Au début, initialisez le mutex:

static pthread_mutex_t printf_mutex;
...
int main()
{
    ...
    pthread_mutex_init(&printf_mutex, NULL);
    ...

Faites ensuite un wrapper autour de printf pour vous assurer que seul le thread qui a obtenu le mutex peut appeler printf (sinon il devra bloquer jusqu'à ce que le mutex est disponible) :

int sync_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    pthread_mutex_lock(&printf_mutex);
    vprintf(format, args);
    pthread_mutex_unlock(&printf_mutex);

    va_end(args);
}
17
Grapsus