Tout le monde dit que les objets immuables sont du fil sûr, mais pourquoi est-ce? strong> p>
Prenez le scénario suivant en cours d'exécution sur une CPU Multi Core: P>
Dans cette situation, lorsque Core 1 demande la valeur à l'emplacement est l'analyse ci-dessus correcte et est une porte de mémoire requise, ou je manque quelque chose? P>
La situation que je décris ici est une version plus complexe de ce qui se passe à chaque fois que la GC a une collecte. Lorsque le GC recueille, la mémoire est réorganisée. Cela signifie que l'emplacement physique de l'objet était situé aux modifications et que L1 / L2 doit être invalidée. À peu près la même chose s'applique à l'exemple ci-dessus. P>
Comme il est raisonnable de s'attendre à ce que .NET garantit qu'après la réorganisation de la mémoire, différents noyaux voient l'état de mémoire correct, la situation ci-dessus ne sera pas un problème aussi. P>
0x100 code> et il est mis en cache dans le cache L1 / L2 du noyau 1; li>
0x100 code> devient disponible pour les nouveaux objets; Li>
0x100 code>; li>
0x100 code>. Li>
ul>
0x100 code> est-il possible de lit les données rassisées de son cache L1 / L2? Mon intuition dit qu'une porte mémoire est toujours nécessaire ici pour vous assurer que le noyau 1 lit les données correctes. P>
4 Réponses :
Vous manquez que ce serait un mauvais collecteur à déchets que laisser une telle chose se produire. La référence sur le noyau 1 aurait dû empêcher l'objet d'être GCD. P>
Je pense que je n'étais pas clair dans ma question. L'idée ici est que Core 1 veut lire le nouvel objet, mais les données de L1 / L2 sont d'un objet que le noyau 1 auparavant avait déjà accès à, mais a été collecté dans la période moyenne.
Ce n'est pas la façon dont le collecteur des ordures fonctionne. Pour donner un filetage une référence à un objet immuable nécessite de garder sa référence stockée quelque part. Donc, vous pouvez le remettre à l'autre fil. Qui garantit automatiquement cela peut être collecté. Les données obsolètes sont bien sûr assez possibles, ce n'est jamais un problème.
Je ne suis pas sûr que la porte de mémoire modifierait ce scénario, car cela ne ferait certainement aucune incidence sur les lectures ultérieures ... et ensuite la question devient lit de l'endroit où em>? S'il s'agit d'un champ (qui doit à un minimum em> être statique ou un champ d'instance pour une instance toujours sur la pile ou autrement accessible), ou une variable locale - alors par définition, il n'est pas disponible pour Collection. P>
RE Le scénario où cette référence est
Désolé, je n'étais pas clair dans ma question. L'idée ici est que le noyau d'objet 1 lit à la dernière étape est l'objet correct et le problème n'est pas si la référence est correcte (voir commentaires et question mise à jour). Core 1 veut réellement lire l'objet créé par Core 2. Ma question est de savoir s'il est garanti que cela lit réellement les données fraîches qu'il attend de lire.
L'immuabilité de l'objet n'est pas la vraie question de votre scénario. Le problème de votre description tourne plutôt autour de la référence, de la liste ou d'un autre système qui pointe vers l'objet. Il aurait bien entendu avoir une sorte de technique pour s'assurer que l'ancien objet n'est plus disponible dans le fil qui aurait peut-être essayé d'y accéder. P>
Le point réel de la sécurité du thread d'objet immutable est que vous em> n'a pas besoin d'écrire un tas de code pour produire une sécurité de fil. Plutôt le cadre, le système d'exploitation, la CPU (et tout autre) fait le travail pour vous. P>
Je pense que je n'étais pas clair dans ma question. J'ai fait quelques mises à jour qui devrait mieux expliquer ma question.
@Pieter: Peut-être que vous avez mal compris ma réponse? Toute porte mémoire requise est déjà en place - et oui, il est probablement requis. Cependant, il est profond dans le système que vous programmez au-dessus, vous pouvez donc écrire votre code comme si aucune porte mémoire n'est nécessaire.
@John Fisher - Comment voulez-vous dire que la porte mémoire est déjà en place?
@Pieter: Le collecteur des ordures, le système d'exploitation, le cadre et / ou la classe que vous utilisez pour gérer les objets ont déjà la synchronisation de fil dans les files. En tant que développeur, vous n'avez pas besoin de m'occuper de cela.
@John Fisher - J'ai raté un point important et j'ai donc demandé la mauvaise question. La même chose se produit à cause de la réorganisation de la mémoire en raison d'une collection de GC. Lorsque la mémoire est réorganisée, les données de l'ancien emplacement de mémoire deviennent invalides et bien sûr, il doit y avoir un mécanisme d'invalidation du cache L1 / L2 dans cette situation également. Il en va de même pour mon exemple, bien que mon exemple ne pose pas la question clairement. Merci de m'avoir envoyé dans la bonne direction.
Je pense que ce que vous demandez est de savoir si, après la création d'un objet, le constructeur revient, et une référence à celle-ci est stockée quelque part, il est possible que un fil sur un autre processeur puisse toujours voir les anciennes données. Vous offrez en tant que scénario la possibilité qu'une ligne de cache contenant des données d'instance de l'objet a été utilisée précédemment à une autre fin. P>
Sous un modèle de mémoire exceptionnellement faible, une telle chose pourrait être possible, mais je m'attendrais à ce que tout modèle de mémoire utile, même relativement faible, garantirait que la déséroférance d'un objet immuable serait sûre, même si ces objets de rembourrage sont nécessaires. assez d'aucune ligne de cache ne doit être partagée entre des instances d'objet (le GC invalidera presque certainement tous les caches lorsqu'il est fait, mais sans ce rembourrage, il serait possible qu'un objet immuable créé par le noyau n ° 2 puisse partager une ligne de cache avec un objet qui Le noyau n ° 1 avait déjà lu). Sans au moins ce niveau de sécurité, l'écriture de code robuste nécessiterait tant de verrous et de barrières de mémoire qu'il serait difficile d'écrire du code multi-processeur qui n'était pas plus lent que le code mono-processeur. P>
Les modèles de mémoire populaires X86 et X64 fournissent la garantie que vous recherchez et aller beaucoup plus loin. Les processeurs coordonnent la «propriété» des lignes de cache; Si plusieurs processeurs veulent lire la même ligne de cache, ils peuvent le faire sans empêchement. Lorsqu'un processeur veut écrire une ligne de cache, elle négocie avec d'autres processeurs pour la propriété. Une fois la propriété acquise, le processeur effectuera l'écriture. D'autres processeurs ne seront pas en mesure de lire ou d'écrire la ligne de cache tant que le processeur qui possède la ligne de cache l'abandonne. Notez que si plusieurs processeurs veulent écrire simultanément la même ligne de cache, ils passeront probablement la majeure partie de leur temps à négocier la propriété de la ligne de cache plutôt que de réaliser des travaux réels, mais une correction sémantique sera préservée. P>
Merci pour ton explication. Oui, c'est la réponse que je cherchais :).
Il y a une différence entre l'immuabilité d'un objet et l'immuabilité (ou un manque de valeur) de la référence.
@Pieter: Je ne sais pas comment GC fonctionne dans .NET mais, pourquoi libérerait-il la mémoire 0x100 puisqu'il y a déjà une référence à l'objet?
@Spender - c'est vrai, mais pour des arguments, disons que nous garantissons que l'adresse à l'objet nouvel objet (immuable) est correcte pour le noyau 1. Cela ne devrait pas changer le scénario. Toute l'idée est que Core 1 veut lire le nouvel objet, mais obtient des résultats escaliers de son cache L1 / L2.
@ user384706 - Parce que l'objet occupé à l'origine
0x100 code> devient admissible à la collecte. (Mise à jour de la question)personnellement i> Je soupçonne que la question ici réside dans la supposition de Bullet 2 sur l'éligibilité; Re le conflit Cross-Core L1 / L2, cela peut dépendre de l'architecture; Comment exactement i> L1 / L2 sont partagés entre les cœurs / HTS dépendent de la puce (en supposant qu'ils se trouvent dans le même emballage de la CPU). Une question trompeuse complexe ...
Je suis d'accord avec certains des autres commentateurs. Dans ce cas, l'objet réel a été immuable, la référence i> n'était pas - NOTE 2 N'a pas muté d'objet immuable, il en a créé un et le mettait à 0x100. La référence de Core 1 est juste que - une référence, et ce n'est pas vraiment une partie de l'objet immuable - c'est beaucoup plus comme un pointeur. Alors Yah - Je pense que vous voudriez que vous voudriez une porte mémoire et je choisirais probablement un échange en verrouillé dans lequel Core 2 Swaps dans l'objet.
@Jmarsch - Oh, à coup sûr - la question complète de l'immutabilité est hors de propos, imo. Dans mon esprit, cela concerne principalement le comportement de référence et de GC avec la mise en cache L1 / L2. En effet, les chances de l'utilisation suivante de 0x100 étant le même type d'objet et sont correctement alignées sont ... mince.
@Marc Gravell - Je vois où tu vas. Yah. C'est un sujet profond. Joe Duffy écrit à propos de cette sorte de chose. J'ai toujours supposé que lorsque le GC effectue une collection, il faut juste être une porte mémoire. Et cela éclaircirait ce scénario, ne serait-ce pas - le GC utilise quoi, une porte de libération (ou peut-être une clôture complète ??), et cela signifie que le noyau 1 reviendra à la mémoire principale lorsqu'il lit cette référence. Est-ce que ça sonne plausible?
Peut-être que je vais mal comprendre quelque chose, mais cette phrase me semble faux: "Pour savoir que j'ai bien compris, il est totalement possible pour le noyau 1 dans cette situation de lire la mémoire à l'emplacement 0x100 de son cache L1 / L2, de sorte qu'il pourrait être lu Les données." Si je viens de changer ce qui est à l'emplacement 0x100, cela invaliderait-il la ligne de cache qui contient cette adresse?
@Jmarsch comme vous dites; Cela semble pas seulement plausible mais une demande essentielle.
@Martinho Fernandes - C'est exactement la question que j'ai b>: est-ce garanti et si oui, comment?
@Pieter: Si c'était votre question, je crois que le mélange avec .NET, GC et l'immuabilité le faisait mal :(. Je pense que c'est uniquement une question sur la cohérence du cache dans un environnement multicœur. Je ne suis pas sûr de Mon affirmation sur l'invalidation ci-dessus, pourriez-vous alors modifier la question pour que cela soit clair, alors quelqu'un qui sait peut dire quelque chose à ce sujet?
@Martinho Fernandes - Je n'étais pas sûr de la façon dont je pourrais faire clairement la question. La raison pour laquelle je pose des questions sur les objets immuables est tout comme un exemple. Avez-vous une suggestion spécifique parce que je suis à perte.
@Martinho et Pieter: la manière dont le cache est invalidé est avec une porte. Toutes les opérations d'échange de verrouillage / comparaison provoquent une porte. Le GC aurait à peu près (bien que je n'ai pas lu le code). Créer l'objet immuable et le mettre à 0x100 n'est pas intrinsèquement threadsafe (encore une fois, j'utiliserais probablement un échange de HTAT), mais après sa définition (et avec une combinaison appropriée autour de l'ensemble), vous en diriez à partir de ce qui devrait être threadsafe ( En supposant que les membres que vous lisez sont également immuables)
@Jmarsch - Cela m'a frappé lorsque je me suis rendu compte qu'un GC collecte également la mémoire de la réorganisation (voir ma question mise à jour) et la même situation s'applique là-bas. Votre analyse semble très plausible. Merci pour votre réponse.
@Pieter Yah, je pense que la clôture de la mémoire prend en charge cela. Au fait, je suis sûr que la seule réorganisation de la mémoire temporelle se produit est sur une collection Gen 2. Je pensais que la réordonnage était une autre raison que les collections de Gen 2 sont beaucoup plus chères que la Gen 0 ou 1. Dans la partie, le GC utilise probablement une clôture sur toutes les collections, signalant ainsi que les caches doivent être manipulées.