2129 votes

C++11 introduit un standardisés modèle de mémoire. Ça veut dire quoi? Et comment est-ce que ça va affecter la programmation en C++?

C++11 introduit un standardisés modèle de mémoire, mais qu'est-ce que cela veut dire? Et comment est-ce que ça va affecter la programmation en C++?

Herb Sutter dit ici que,

Le modèle de mémoire signifie que le code C++ a maintenant un normalisée de la bibliothèque de l'appeler peu importe qui fait le compilateur et sur quelle plate-forme il est en cours d'exécution. Il y a un moyen standard pour contrôler la façon dont les différents threads de parler à la la mémoire du processeur.

"Quand vous parlez de fractionnement [code] à travers les différents cœurs du dans la norme, nous parlons d' le modèle de mémoire. Nous allons optimiser sans les casser l' suivant les hypothèses les gens vont pour faire dans le code", Sutter a déclaré.

Eh bien, je peux mémoriser ces paragraphes disponible en ligne (comme je l'ai mon propre modèle de mémoire depuis la naissance de l' :P), et peuvent même poste en tant que réponse à des questions posées par d'autres, mais pour être honnête, je n'ai pas de comprendre exactement ce.

Donc, fondamentalement, ce que je veux savoir, c'est, programmeurs C++ utilisé pour développer des applications multi-thread avant même, donc, comment est-il importe si son threads POSIX, ou des threads Windows, ou C++11 fils? Quels sont les avantages? Je veux comprendre les détails de bas niveau.

J'ai aussi ce sentiment que le C++11 modèle de mémoire est en quelque sorte lié à C++11 support du multi-thread, je vois souvent les deux ensemble. Si elle l'est, comment exactement? Pourquoi devraient-ils être liés?

Comme je ne sais pas comment internes de multi-threading qui fonctionne et ce modèle de mémoire signifie, en général, s'il vous plaît aidez-moi à comprendre ces concepts. :-)

2486voto

Nemo Points 32838

Tout d'abord, vous devez apprendre à penser comme un Langage Avocat.

Le C++ spécification ne fait pas référence à un compilateur, système d'exploitation, ou CPU. Il fait référence à une machine abstraite qui est une généralisation de systèmes réels. Dans la Langue de l'Avocat du monde, le travail du programmeur consiste à écrire du code pour la machine abstraite; le travail du compilateur est de concrétiser ce code sur un béton de la machine. Par le codage de manière rigide à la spec, vous pouvez être certain que votre code de compiler et de l'exécuter sans modification sur n'importe quel système avec un conforme compilateur C++, que ce soit aujourd'hui ou 50 ans à partir de maintenant.

La machine abstraite dans le C++98/C++03 spécification est fondamentalement single-threaded. Donc, il n'est pas possible d'écrire multi-threaded code C++ qui est "portable" à l'égard de la spec. La spec n'a même pas dit rien sur l' atomicité de la mémoire des charges et des magasins ou dans l' ordre dans lequel les charges et les magasins qui pourrait arriver, jamais l'esprit des choses comme les mutex.

Bien sûr, vous pouvez écrire le code multithread, dans la pratique, pour certains systèmes concrets-comme pthreads ou Windows. Mais il n'y a pas de standard façon d'écrire le code multithread pour C++98/C++03.

La machine abstraite en C++11 est multi-thread de par leur conception. Il dispose également d'un bien définis modèle de mémoire; c'est, dit-il ce que le compilateur peut et ne peut pas faire quand il s'agit de l'accès à la mémoire.

Prenons l'exemple suivant, où une paire de variables globales sont accessibles simultanément par deux fils:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

Ce qui pourrait Thread 2 sortie?

Sous C++98/C++03, ce n'est même pas un Comportement Indéfini; la question elle-même est vide de sens parce que la norme ne prévoit pas quelque chose appelé un "thread".

En vertu de C++11, le résultat est un Comportement Indéfini, parce que les charges et les magasins n'ont pas besoin d'être atomique en général. Qui peut ne pas sembler une amélioration... Et par lui-même, il n'est pas.

Mais avec le C++11, vous pouvez écrire ceci:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

Maintenant les choses deviennent beaucoup plus intéressantes. Tout d'abord, le comportement est défini. Thread 2 peut maintenant imprimer 0 0 (si elle s'exécute avant que le Thread 1), en 37 17 (si il court après le Thread 1), ou 0 17 (si il court après le Thread 1 attribue à x, mais avant qu'il assigne à y).

Ce qu'il ne peut pas imprimer est - 37 0, parce que le mode par défaut pour l'charges/magasins en C++11 est de mettre en cohérence séquentielle. Cela signifie simplement que toutes les charges et les magasins doivent être "comme si" ils s'est passé dans l'ordre que vous avez écrit dans chaque thread, tandis que les opérations entre les threads peuvent être entrelacées cependant, le système aime. Ainsi, le comportement par défaut de atomics fournit à la fois l'atomicité et de commande pour les charges et les magasins.

Maintenant, sur un PROCESSEUR récent, assurer la cohérence séquentielle peut être coûteux. En particulier, le compilateur est susceptible d'émettre de plein fouet les barrières de la mémoire entre chaque accès ici. Mais si votre algorithme peut tolérer de charges et magasins; c'est à dire, si elle nécessite d'atomicité, mais pas la commande; c'est à dire, si l'on peut tolérer 37 0 que la sortie de ce programme, alors vous pouvez écrire ceci:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

Le plus moderne, le CPU, le plus probable c'est d'être plus rapide que dans l'exemple précédent.

Enfin, si vous avez juste besoin de garder certaines charges particulières et les stocke dans l'ordre, vous pouvez écrire:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

Cela nous ramène à la commande, les charges et les magasins -- 37 0 n'est plus une sortie possible -- mais il le fait avec un minimum de frais généraux. (Dans cet exemple trivial, le résultat est le même que complet à la cohérence séquentielle; dans un programme plus large, elle ne serait pas.)

Bien sûr, si les seules sorties que vous voulez voir sont 0 0 ou 37 17, vous pouvez juste envelopper un mutex à travers le code d'origine. Mais si vous avez lu jusqu'ici, je parie que vous savez déjà comment cela fonctionne, et cette réponse, c'est déjà plus long que j'ai prévu :-).

Donc, la ligne du bas. Les mutex sont grands, et le C++11 normalise. Mais parfois, pour des raisons de performance que vous voulez fonctions primitives de bas niveau (par exemple, le classique double-vérifier le verrouillage de modèle). La nouvelle norme fournit des gadgets comme les mutex et les variables de condition, et il fournit également un faible niveau de gadgets comme les types atomiques et les différentes saveurs de la mémoire de la barrière. Alors maintenant, vous pouvez écrire sophistiquée, hautes performances simultanées routines entièrement dans la langue spécifiée par la norme, et vous pouvez être certain que votre code de compiler et d'exécuter de manière identique sur les deux aujourd'hui et de demain.

Bien que, pour être franc, sauf si vous êtes un expert et de travail sur certaines graves au code de bas niveau, vous devriez probablement s'en tenir à mutex et les variables de condition. C'est ce que j'ai l'intention de le faire.

Pour en savoir plus sur ce genre de choses, voir ce billet de blog.

138voto

eran Points 12628

C'est maintenant à 2 ans question, mais étant très populaire, il est utile de mentionner une ressource fantastique pour apprendre le C++11 modèle de mémoire. Je ne vois pas en résumant son discours afin de rendre le présent encore une autre réponse, mais étant donné que c'est le gars qui a écrit le standard, je pense que c'est bien la peine de regarder le talk.

Herb Sutter a 3 heures à parler du C++11 modèle de mémoire intitulé "atomique<> Armes", disponible sur le site de channel 9 - partie 1 et partie 2. Le discours est assez technique, et couvre les sujets suivants:

  1. Les optimisations, les Courses, et le Modèle de Mémoire
  2. Commande – Ce que: Acquérir et de Libération
  3. Commande – Comment: Mutex, Atomics, et/ou des Clôtures
  4. D'autres Restrictions sur les Compilateurs et Matériel
  5. Code Gen & Performance: x86/x64, IA64, la PUISSANCE, le BRAS
  6. Détendu Atomics

Le discours ne m'étendrai pas sur l'API, mais plutôt sur le raisonnement, l'arrière-plan, sous le capot et coulisses (le saviez-vous détendu sémantique ont été ajoutées à la norme uniquement parce que la Puissance et le Bras ne prennent pas en charge synchronisé charge de manière efficace?).

81voto

Puppy Points 90818

Ce que cela signifie, c'est que la Norme définit maintenant le multi-threading, il définit ce qui se passe dans le contexte de plusieurs threads. Bien sûr, les gens ont utilisé diverses implémentations, mais c'est comme demander pourquoi nous devrions avoir un std::string alors que nous pourrions tous être à l'aide d'un laminé string classe. Quand vous parlez des threads POSIX ou des threads Windows, alors c'est un peu une illusion, comme en fait vous parlez x86 fils, comme c'est une fonction matérielle à exécuter simultanément. Le C++0x mémoire modèle de garanties, si vous êtes sur x86 ou ARM ou Mips, ou quoi que ce soit d'autre, vous pouvez venir avec.

60voto

ritesh Points 546

Pour les langues qui ne sont pas la spécification d'un modèle de mémoire, vous écrivez du code de la langue et de la mémoire de modèle spécifié par l'architecture du processeur. Le processeur peut choisir de commander à nouveau accès à la mémoire pour la performance. Donc, si votre programme a données courses (données de la course, c'est quand son possible pour de multiples coeurs / hyper-threads d'accéder à la même mémoire simultanément) puis votre programme n'est pas de la croix-plate-forme en raison de sa dépendance sur le processeur, la mémoire de modèle. Vous pouvez consulter la carte Intel ou AMD manuels de logiciel pour savoir comment les processeurs peuvent réorganiser les accès à la mémoire.

Très important, les verrous (et de la simultanéité de la sémantique avec verrouillage) sont généralement mis en œuvre dans une plate-forme de manière... donc si vous êtes en utilisant la norme de serrures dans un programme multithread avec pas de données courses, alors vous n'avez pas à vous soucier de la croix-plate-forme de modèles de mémoire.

Fait intéressant, les compilateurs Microsoft pour C++ ont acquisition / diffusion de la sémantique de la volatilité qui est une extension C++ pour faire face au manque d'un modèle de mémoire en C++ [http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs. 80).aspx]. Toutefois, étant donné que Windows s'exécute sur x86 / x64 seulement, ce n'est pas peu dire (Intel et AMD modèles de mémoire, il est facile et efficace pour mettre en œuvre d'acquisition / diffusion de la sémantique dans une langue).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X