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?
La spécification POSIX comprend ces fonctions:
getc_unlocked()
getchar_unlocked()
putc_unlocked()
putchar_unlock()
Versions des fonctions
getc()
,getchar()
,putc()
etputchar()
respectivement nomméesgetc_unlocked()
,getchar_unlocked()
,putc_unlocked()
etputchar_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 parflockfile()
(ouftrylockfile()
) etfunlockfile()
. 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()
ouftrylockfile()
.
Les spécifications de ces fonctions mentionnent:
flockfile()
ftrylockfile()
funlockfile()
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 utilisaientflockfile()
etfunlockfile()
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 fonctionflockfile()
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()
etprintf()
sont imprimés comme sifputc()
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()
etputchar_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 fonctionsflockfile()
ouftrylockfile()
.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).
Réécrit le 26/07/2017.
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.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…
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);
}