Dépendance des données
Si deux opérations accèdent à la même variable et que l'une des deux opérations est une opération d'écriture, il existe une dépendance de données entre les deux opérations. Les dépendances de données sont divisées en trois types suivants :
Nom
Exemple de code
Description
Lire après l'écriture
a = 1;b = a;
Après avoir écrit une variable, lisez ceci emplacement .
Écrire après avoir écrit
a = 1;a = 2;
Après avoir écrit une variable, écrivez à nouveau cette variable.
Lire puis écrire
a = b;b = 1;
Après avoir lu une variable, écrivez à nouveau la variable.
Dans les trois cas ci-dessus, tant que l'ordre d'exécution des deux opérations est réorganisé, le résultat de l'exécution du programme sera modifié.
Comme mentionné précédemment, les compilateurs et les processeurs peuvent réorganiser les opérations. Le compilateur et le processeur respecteront les dépendances de données lors de la réorganisation, et le compilateur et le processeur ne modifieront pas l'ordre d'exécution de deux opérations ayant des dépendances de données.
Notez que les dépendances de données mentionnées ici se réfèrent uniquement à la séquence d'instructions exécutées dans un seul processeur et les opérations exécutées dans un seul thread ne sont pas compilées.
sémantique comme si-série
la sémantique comme si-série signifie : peu importe la façon dont vous réorganisez (les compilateurs et les processeurs afin d'améliorer le parallélisme), le programme (à thread unique) Le les résultats de l'exécution ne peuvent pas être modifiés. Le compilateur, l'environnement d'exécution et le processeur doivent tous adhérer à une sémantique similaire à celle d'une série.
Afin de se conformer à la sémantique « comme si série », le compilateur et le processeur ne réordonneront pas les opérations avec des dépendances de données, car une telle réorganisation modifiera les résultats de l'exécution. Cependant, s'il n'y a pas de dépendances de données entre les opérations, ces opérations peuvent être réorganisées par le compilateur et le processeur. Pour illustrer spécifiquement, veuillez regarder l'exemple de code suivant pour calculer l'aire d'un cercle :
double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
Les dépendances de données des trois opérations ci-dessus sont présentées ci-dessous :
Comme le montre la figure ci-dessus, il existe une relation de dépendance aux données entre A et C, et il existe également une relation de dépendance aux données entre B et C. Par conséquent, dans la séquence d'instructions finalement exécutée, C ne peut pas être réorganisé avant A et B (C est placé devant A et B, et le résultat du programme sera modifié). Mais il n’y a aucune dépendance de données entre A et B, et le compilateur et le processeur peuvent réorganiser l’ordre d’exécution entre A et B. La figure suivante montre les deux séquences d'exécution du programme :
la sémantique comme si-série protège les programmes monothread et se conforme aux La sémantique en série du compilateur, du runtime et du processeur fonctionne ensemble pour créer l'illusion pour les programmeurs écrivant des programmes monothread que les programmes monothread sont exécutés dans l'ordre des programmes. La sémantique comme si la série évite aux programmeurs monothread d'avoir à se soucier de la réorganisation qui interfère avec eux, et ils n'ont pas à se soucier des problèmes de visibilité de la mémoire.
Règles de séquence du programme
Selon les règles de séquence du programme arrive-avant, l'exemple de code ci-dessus pour calculer l'aire d'un cercle a trois relations arrive-avant :
A arrive - avant B;
B arrive-avant C;
A arrive-avant C
La troisième relation arrive-avant ici est dérivée sur la base de la transitivité de arrive-avant;
Ici, A se produit avant B, mais dans l'exécution réelle, B peut être exécuté avant A (voir l'ordre d'exécution réorganisé ci-dessus). Comme mentionné au chapitre 1, si A se produit avant B, JMM n'exige pas que A soit exécuté avant B. JMM exige seulement que l'opération précédente (résultat de l'exécution) soit visible par l'opération suivante, et que l'opération précédente précède la deuxième opération dans l'ordre. Ici, le résultat de l'exécution de l'opération A n'a pas besoin d'être visible pour l'opération B ; et le résultat de l'exécution après la réorganisation de l'opération A et de l'opération B est cohérent avec le résultat de l'exécution de l'opération A et de l'opération B dans l'ordre qui se produit avant. Dans ce cas, JMM considérera que cette réorganisation n'est pas illégale (pas illégale), et JMM autorise cette réorganisation.
En informatique, la technologie logicielle et la technologie matérielle ont un objectif commun : développer autant de parallélisme que possible sans modifier les résultats d'exécution du programme. Les compilateurs et les processeurs adhèrent à cet objectif. D'après la définition de ce qui se passe avant, nous pouvons voir que JMM adhère également à cet objectif.
L'impact de la réorganisation sur le multi-threading
Voyons maintenant si la réorganisation modifiera les résultats d'exécution des programmes multi-thread. Veuillez regarder l'exemple de code suivant :
class ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } Public void reader() { if (flag) { //3 int i = a * a; //4 …… } } }
La variable flag est un indicateur utilisé pour identifier si la variable a a été écrite. Supposons ici qu’il existe deux threads A et B. A exécute d’abord la méthodewriter(), puis le thread B exécute la méthode reader(). Lorsque le thread B effectue l'opération 4, peut-il voir le thread A écrire dans la variable partagée a lors de l'opération 1 ?
La réponse est : pas forcément visible.
Étant donné que l'opération 1 et l'opération 2 n'ont aucune dépendance de données, le compilateur et le processeur peuvent réorganiser ces deux opérations ; de la même manière, l'opération 3 et l'opération 4 n'ont aucune dépendance de données, le compilateur et le processeur peuvent donc réorganiser ces deux opérations. Voyons d'abord ce qui pourrait se passer lorsque l'opération 1 et l'opération 2 sont réorganisées ? Veuillez consulter le chronogramme d'exécution du programme ci-dessous :
Comme le montre la figure ci-dessus, l'opération 1 et l'opération 2 ont été réorganisées. Lorsque le programme est exécuté, le thread A écrit d'abord la variable flag, puis le thread B lit cette variable. Puisque la condition est vraie, le thread B lira la variable a. Pour le moment, la variable a n'a pas du tout été écrite par le thread A, et la sémantique du programme multi-thread ici est détruite par réorganisation !
※Remarque : Dans cet article, les lignes de flèches en pointillés rouges sont utilisées pour indiquer des opérations de lecture incorrectes, et les lignes de flèches en pointillés vertes sont utilisées pour indiquer les opérations de lecture correctes.
Regardons ce qui se passera lorsque les opérations 3 et 4 seront réorganisées (avec l'aide de cette réorganisation, nous pouvons également expliquer les dépendances de contrôle). Voici le chronogramme d'exécution du programme une fois les opérations 3 et 4 réorganisées :
Dans le programme, il existe une dépendance de contrôle entre l'opération 3 et l'opération 4. Lorsqu'il existe des dépendances de contrôle dans le code, cela affecte le degré de parallélisme dans l'exécution de la séquence d'instructions. À cette fin, les compilateurs et les processeurs utilisent l’exécution de spéculation pour surmonter l’impact des dépendances de contrôle sur le parallélisme. En prenant l'exécution spéculative du processeur comme exemple, le processeur exécutant le thread B peut lire et calculer a*a à l'avance, puis enregistrer temporairement les résultats du calcul dans un cache matériel appelé tampon de réorganisation ROB. Lorsque la condition de l'opération suivante 3 est jugée vraie, le résultat du calcul est écrit dans la variable i.
D'après la figure, nous pouvons voir que deviner l'exécution réordonne essentiellement les opérations 3 et 4. La réorganisation brise ici la sémantique des programmes multithread !
Dans un programme monothread, la réorganisation des opérations avec des dépendances de contrôle ne modifiera pas les résultats de l'exécution (c'est pourquoi la sémantique comme si la série permet de réorganiser les opérations avec des dépendances de contrôle) ; la réorganisation des opérations qui ont des dépendances de contrôle peut modifier les résultats d'exécution du programme.
Ce qui précède est une analyse approfondie du modèle de mémoire Java : réorganisation du contenu. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !