web-dev-qa-db-fra.com

stdout thread-safe en C sous Linux?

L'écriture sur stdout utilise-t-elle printf thread-safe sur Linux? Qu'en est-il de l'utilisation de la commande write de niveau inférieur?

41
Claudiu

Il n'est pas spécifié par la norme C - cela dépend de votre implémentation de la bibliothèque de normes C. En fait, la norme C ne mentionne même pas du tout les threads, car certains systèmes (par exemple les systèmes embarqués) n'ont pas de multithreading.

Dans l'implémentation GNU (glibc), la plupart des fonctions de niveau supérieur de stdio qui traitent les objets FILE* Sont thread-safe. Celles qui ne sont pas n'ont généralement pas unlocked dans leurs noms (par exemple getc_unlocked(3)). Cependant, la sécurité du thread est au niveau de l'appel par fonction: si vous effectuez plusieurs appels à printf(3), par exemple, chacun de ces appels est garanti pour sortir atomiquement, mais d'autres threads peuvent imprimer des choses entre vos appels à printf(). Si vous voulez vous assurer qu'une séquence d'appels d'E/S est sortie atomiquement , vous pouvez les entourer d'une paire d'appels flockfile(3)/funlockfile(3) pour verrouiller la poignée FILE. Notez que ces fonctions sont réentrantes, vous pouvez donc appeler en toute sécurité printf() entre elles , et cela n'entraînera pas de blocage, même si printf() lui-même appelle flockfile().

Les appels d'E/S de bas niveau tels que write(2) devraient être thread-safe, mais je n'en suis pas sûr à 100% - write() effectue un appel système dans le noyau pour effectuer E/S. La manière exacte dont cela se produit dépend du noyau que vous utilisez. Il peut s'agir de l'instruction sysenter ou de l'instruction int (interruption) sur les anciens systèmes. Une fois à l'intérieur du noyau, c'est au noyau de s'assurer que les E/S sont thread-safe. Dans un test que je viens de faire avec la version 8.11.1 de Darwin Kernel, write(2) semble être thread-safe.

55
Adam Rosenfield

Que vous l'appeliez "thread-safe" dépend de votre définition de thread-safe. POSIX nécessite des fonctions stdio pour utiliser le verrouillage, donc votre programme ne se bloquera pas, corrompra les états de l'objet FILE, etc. si vous utilisez printf simultanément à partir de plusieurs threads. Cependant, toutes les opérations stdio sont formellement spécifiées en termes d'appels répétés à fgetc et fputc, il n'y a donc pas d'atomicité à plus grande échelle garantie. Autrement dit, si les fils 1 et 2 essaient d'imprimer "Hello\n" et "Goodbye\n" en même temps, rien ne garantit que la sortie sera soit "Hello\nGoodbye\n" ou "Goodbye\nHello\n". Cela pourrait tout aussi bien être "HGelolodboy\ne\n". En pratique, la plupart des implémentations acquerront un seul verrou pour tout l'appel en écriture de niveau supérieur simplement parce qu'il est plus efficace, mais votre programme ne devrait pas le supposer. Il peut y avoir des cas de coin où cela n'est pas fait; par exemple, une implémentation pourrait probablement totalement omettre le verrouillage sur les flux sans tampon.

Edit: Le texte ci-dessus sur l'atomicité est incorrect. POSIX garantit que toutes les opérations stdio sont atomiques, mais la garantie est masquée dans la documentation de flockfile: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile .html

Toutes les fonctions qui référencent des objets (FILE *) doivent se comporter comme si elles utilisaient flockfile () et funlockfile () en interne pour obtenir la propriété de ces objets (FILE *).

Vous pouvez utiliser vous-même les fonctions flockfile, ftrylockfile et funlockfile pour réaliser des écritures atomiques d'appel plus grandes que la fonction unique.

Ils sont tous deux thread-safe au point que votre application ne se bloquera pas si plusieurs threads les appellent sur le même descripteur de fichier. Cependant, sans verrouillage au niveau de l'application, tout ce qui est écrit pourrait être entrelacé.

16
Dave Ray

C a obtenu une nouvelle norme depuis que cette question a été posée (et la dernière réponse).

C11 est désormais livré avec un support multithreading et traite le comportement multithread des flux:

§7.21.2 Flux

¶7 Chaque flux a un verrou associé qui est utilisé pour empêcher les courses de données = lorsque plusieurs threads d'exécution accèdent à un flux, et à restreindre l'entrelacement des opérations de flux effectuées par plusieurs threads. Un seul fil peut contenir ce verrou à la fois. Le verrou est réentrant: un seul thread peut maintenir le verrou plusieurs fois à un moment donné.

¶8 Toutes les fonctions qui lisent, écrivent, positionnent ou interrogent la position d'un flux verrouillent le flux avant d'y accéder. Ils libèrent le verrou associé au flux lorsque l'accès est terminé.

Ainsi, une implémentation avec des threads C11 doit garantir que l'utilisation de printf est thread-safe.

Que l'atomicité (comme dans aucun entrelacement1) est garanti, ce n'était pas clair pour moi à première vue, car la norme parlait de restreindre entrelacé, par opposition à empêcher, ce qu'elle a mandaté pour les courses de données.

Je penche pour qu'il soit garanti. La norme parle de restreindre entrelacement, car certains entrelacement qui ne changent pas le résultat peuvent toujours se produire; par exemplefwrite quelques octets, fseek en arrière et fwrite jusqu'à l'offset d'origine, de sorte que les deux fwrites soient dos à dos. L'implémentation est libre de réorganiser ces 2 fwrite et de les fusionner en une seule écriture.


1 : Voir le texte barré dans réponse de R .. pour un exemple.

8
a3f

C'est thread-safe; printf devrait être réentrant et vous ne causerez aucune étrangeté ou corruption dans votre programme.

Vous ne pouvez pas garantir que votre sortie d'un thread ne commencera pas à mi-chemin de la sortie d'un autre thread. Si vous vous souciez de cela, vous devez développer votre propre code de sortie verrouillé pour empêcher les accès multiples.

6
Adam Hawes