web-dev-qa-db-fra.com

Cout est-il synchronisé / thread-safe?

En général, je suppose que les flux ne sont pas synchronisés, c'est à l'utilisateur de faire le verrouillage approprié. Cependant, des choses comme cout bénéficient-elles d'un traitement spécial dans la bibliothèque standard?

Autrement dit, si plusieurs threads écrivent dans cout peuvent-ils corrompre l'objet cout? Je comprends que même si synchronisé, vous obtiendrez toujours une sortie entrelacée de manière aléatoire, mais cet entrelacement est-il garanti. Autrement dit, est-il sûr d'utiliser cout à partir de plusieurs threads?

Ce fournisseur dépend-il? Que fait gcc?


Important: Veuillez fournir une sorte de référence pour votre réponse si vous dites "oui" car j'ai besoin d'une sorte de preuve de cela.

Ma préoccupation ne concerne pas non plus les appels système sous-jacents, ceux-ci sont corrects, mais les flux ajoutent une couche de tampon en haut.

108
edA-qa mort-ora-y

La norme C++ 03 n'en dit rien. Lorsque vous n'avez aucune garantie sur la sécurité des threads de quelque chose, vous devez le traiter comme non thread-safe.

Le fait que cout soit tamponné est particulièrement intéressant. Même si les appels à write (ou tout ce qui accomplit cet effet dans cette implémentation particulière) sont garantis mutuellement exclusifs, le tampon peut être partagé par les différents threads. Cela entraînera rapidement une corruption de l'état interne du flux.

Et même si l'accès au tampon est garanti pour les threads, que pensez-vous qu'il se passera dans ce code?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

Vous voulez probablement que chaque ligne ici agisse en exclusion mutuelle. Mais comment une implémentation peut-elle garantir cela?

En C++ 11, nous avons quelques garanties. Le FDIS dit ce qui suit au §27.4.1 [iostream.objects.overview]:

L'accès simultané aux fonctions d'entrée et de sortie formatées et non formatées d'un objet iostream standard synchronisé (§27.5.3.4) (§27.7.2.1) et de sortie (§27.7.3.1) ou à un flux C standard par plusieurs threads ne doit pas entraîner de course aux données (§ 1.10). [Remarque: Les utilisateurs doivent toujours synchroniser l'utilisation simultanée de ces objets et flux par plusieurs threads s'ils souhaitent éviter les caractères entrelacés. - note de fin]

Ainsi, vous n'obtiendrez pas de flux corrompus, mais vous devez toujours les synchroniser manuellement si vous ne voulez pas que la sortie soit ordonnée.

102

C'est une excellente question.

Tout d'abord, C++ 98/C++ 03 n'a pas de concept de "thread". Donc, dans ce monde, la question n'a pas de sens.

Qu'en est-il de C++ 0x? Voir réponse de Martinho (ce qui, je l'avoue, m'a surpris).

Qu'en est-il des implémentations spécifiques pré-C++ 0x? Eh bien, par exemple, voici le code source de basic_streambuf<...>:sputc de GCC 4.5.2 (en-tête "streambuf"):

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

De toute évidence, cela n'effectue aucun verrouillage. Et xsputn non plus. Et c'est certainement le type de streambuf que cout utilise.

Autant que je sache, libstdc ++ n'effectue aucun verrouillage autour des opérations de flux. Et je ne m'attendrais à aucun, car ce serait lent.

Donc, avec cette implémentation, il est évidemment possible que la sortie de deux threads se corrompre ( pas juste entrelacer).

Ce code pourrait-il corrompre la structure de données elle-même? La réponse dépend des interactions possibles de ces fonctions; par exemple, que se passe-t-il si un thread essaie de vider le tampon tandis qu'un autre essaie d'appeler xsputn ou autre chose. Cela peut dépendre de la façon dont votre compilateur et votre processeur décident de réorganiser les chargements et les mémoires; il faudrait une analyse minutieuse pour en être sûr. Cela dépend également de ce que fait votre CPU si deux threads tentent de modifier le même emplacement simultanément.

En d'autres termes, même si cela fonctionne correctement dans votre environnement actuel, il peut se briser lorsque vous mettez à jour votre runtime, votre compilateur ou votre CPU.

Résumé: "Je ne le ferais pas". Créez une classe de journalisation qui verrouille correctement ou passez à C++ 0x.

Comme alternative faible, vous pouvez définir cout sans tampon. Il est probable (mais non garanti) de sauter toute logique liée au tampon et d'appeler directement write. Bien que cela puisse être excessivement lent.

15
Nemo

La norme C++ ne spécifie pas si l'écriture dans les flux est compatible avec les threads, mais ce n'est généralement pas le cas.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

et aussi: Les flux de sortie standard en C++ sont-ils sûrs pour les threads (cout, cerr, clog)?

[~ # ~] mise à jour [~ # ~]

Veuillez consulter la réponse de @Martinho Fernandes pour savoir ce que le nouveau standard C++ 11 raconte à ce sujet.

7
phoxis

Comme d'autres réponses le mentionnent, cela est certainement spécifique au fournisseur car la norme C++ ne fait aucune mention du threading (cela change en C++ 0x).

GCC ne fait pas beaucoup de promesses concernant la sécurité des threads et les E/S. Mais la documentation pour ce qu'il promet est ici:

l'essentiel est probablement:

Le type __basic_file est simplement une collection de petits wrappers autour de la couche C stdio (encore une fois, voir le lien sous Structure). Nous ne nous verrouillons pas, mais passons simplement aux appels à fopen, fwrite, etc.

Ainsi, pour la version 3.0, il faut répondre à la question "le multithreading est-il sûr pour les E/S", "est-ce que la bibliothèque C de votre plateforme est sécurisée pour les E/S?" Certains le sont par défaut, d'autres non; beaucoup offrent plusieurs implémentations de la bibliothèque C avec des compromis différents en matière de sécurité et d'efficacité des threads. Vous, le programmeur, devez toujours prendre soin de plusieurs threads.

(Par exemple, la norme POSIX requiert que les opérations C stdio FILE * soient atomiques. Les bibliothèques C conformes à POSIX (par exemple, sur Solaris et GNU/Linux) ont un mutex interne pour sérialiser les opérations sur FILE * s. Cependant, vous avez toujours besoin pour ne pas faire des choses stupides comme appeler fclose (fs) dans un thread suivi d'un accès à fs dans un autre.)

Ainsi, si la bibliothèque C de votre plate-forme est threadsafe, vos opérations d'E/S fstream seront threadsafe au niveau le plus bas. Pour les opérations de niveau supérieur, telles que la manipulation des données contenues dans les classes de formatage de flux (par exemple, la configuration de rappels dans un std :: ofstream), vous devez protéger ces accès comme toute autre ressource partagée critique.

Je ne sais pas si quelque chose a changé depuis le calendrier 3.0 mentionné.

La documentation de MSVC sur la sécurité des threads pour iostreams peut être trouvée ici: http://msdn.Microsoft.com/en-us/library/c9ceah3b.aspx :

Un objet unique est thread-safe pour la lecture à partir de plusieurs threads. Par exemple, étant donné un objet A, il est sûr de lire A à partir du thread 1 et du thread 2 simultanément.

Si un seul objet est écrit par un thread, toutes les lectures et écritures sur cet objet sur le même thread ou sur d'autres threads doivent être protégées. Par exemple, étant donné un objet A, si le thread 1 écrit dans A, alors le thread 2 doit être empêché de lire ou d'écrire dans A.

Il est sûr de lire et d'écrire sur une instance d'un type même si un autre thread lit ou écrit sur une autre instance du même type. Par exemple, étant donné les objets A et B du même type, il est sûr si A est écrit dans le thread 1 et B est lu dans le thread 2.

...

Classes iostream

Les classes iostream suivent les mêmes règles que les autres classes, à une exception près. Il est sûr d'écrire sur un objet à partir de plusieurs threads. Par exemple, le thread 1 peut écrire dans cout en même temps que le thread 2. Cependant, cela peut entraîner le mélange des sorties des deux threads.

Remarque: La lecture à partir d'un tampon de flux n'est pas considérée comme une opération de lecture. Elle doit être considérée comme une opération d'écriture, car cela modifie l'état de la classe.

Notez que ces informations concernent la version la plus récente de MSVC (actuellement pour VS 2010/MSVC 10/cl.exe 16.x). Vous pouvez sélectionner les informations pour les anciennes versions de MSVC à l'aide d'un contrôle déroulant sur la page (et les informations sont différentes pour les anciennes versions).

6
Michael Burr