À quoi servent les clôtures de mémoire en Java?


Tout en essayant de comprendre comment SubmissionPublisher (code source en Java SE 10, OpenJDK | docs), une nouvelle classe ajoutée à Java SE dans la version 9, a été implémentée, je suis tombé sur quelques appels d'API pour VarHandle Je n'étais pas au courant auparavant de:

fullFence, acquireFence, releaseFence, loadLoadFence et storeStoreFence.

Après avoir fait quelques recherches, en particulier concernant le concept de barrières/clôtures de mémoire (j'en ai déjà entendu parler, oui; mais je ne les ai jamais utilisées, donc était assez peu familier avec leur sémantique), je pense que j'ai une compréhension de base de ce qu'ils sont pour. Néanmoins, comme mes questions peuvent provenir d'une idée fausse, je veux m'assurer que j'ai bien compris en premier lieu:

  1. Les barrières de mémoire sont des contraintes de réorganisation concernant les opérations de lecture et d'écriture.

  2. Les barrières de mémoire peuvent être classées en deux catégories principales: les barrières de mémoire unidirectionnelles et bidirectionnelles, selon que ils définissent des contraintes sur les lectures ou les écritures, ou les deux.

  3. C++ prend en charge une variété de barrières de mémoire, cependant, celles-ci ne correspondent pas à celles fournies par VarHandle. Cependant, certaines des barrières de mémoire disponibles dans VarHandlefournissent des effets de commandequi sont compatibles avec leurs barrières de mémoire C++ correspondantes.

    • {[9] } est compatible avec atomic_thread_fence(memory_order_seq_cst)
    • {[11] } est compatible avec atomic_thread_fence(memory_order_acquire)
    • #releaseFence est compatible avec atomic_thread_fence(memory_order_release)
    • #loadLoadFence et #storeStoreFence n'ont pas de contre-partie C++ compatible

Le mot compatible semble vraiment important ici car la sémantique diffère clairement en ce qui concerne les détails. Par exemple, toutes les barrières C++ sont bidirectionnelles, alors que les barrières Java ne le sont pas (nécessairement).

  1. La plupart des barrières de mémoire ont également des effets de synchronisation.{[25] } Ceux-ci dépendent en particulier du type de barrière utilisé et instructions de barrière précédemment exécutées dans d'autres threads. Comme toutes les implications d'une instruction barrier sont spécifiques au matériel, je vais m'en tenir aux barrières de niveau supérieur (C++). En C++, par exemple, les modifications apportées avant la communiqué de obstacle à l'instruction sont visibles à un thread exécutant un acquérir barrière de l'instruction.

Mes hypothèses sont-elles correctes? Si oui, mes questions résultantes sont:

  1. Faites les barrières de mémoire disponibles dans VarHandle cause tout type de synchronisation de la mémoire?

  2. Qu'elles provoquent ou non la synchronisation de la mémoire, à quoi peuvent servir les contraintes de réorganisation en Java? Le modèle de mémoire Java donne déjà des garanties très fortes concernant la commande lorsque des champs volatils, des verrous ou des opérations VarHandle comme #compareAndSet sont impliqués.

Dans le cas où vous cherchez un exemple: Le BufferedSubscription susmentionné, une classe interne de SubmissionPublisher (source liée ci-dessus), a établi un clôture dans la ligne 1079 (function growAndAdd; comme le site Web lié ne prend pas en charge les identifiants de fragment, il suffit de CTRL+F pour cela). Cependant, il n'est pas clair pour moi à quoi il sert.

Author: Holger, 2020-02-07

1 answers

C'est surtout une non-réponse, vraiment (au départ je voulais faire un commentaire, mais comme vous pouvez le voir, c'est beaucoup trop long). C'est juste que j'ai interrogé moi-même beaucoup, fait beaucoup de lecture et de recherche et à ce moment, je peux dire: c'est compliqué. J'ai même écrit plusieurs tests avec jcstress pour comprendre comment ils fonctionnent vraiment (tout en regardant le code assembleur généré) et alors que certains d'entre eux en quelque sorte avaient du sens, le sujet en général n'est en aucun cas facile.

, La première chose que vous devez comprendre:

La spécification du langage Java (JLS) ne mentionne pas les barrières , n'importe où. Ceci, pour java, serait un détail d'implémentation: il agit vraiment en termes de se produit avant sémantique. Pour pouvoir les spécifier correctement en fonction du JMM (Modèle de mémoire Java), le JMM devrait changer beaucoup.

Il s'agit de travaux en cours.

Deuxièmement, si vous je veux vraiment gratter la surface ici, c'est la toute première chose à regarder . Le discours est incroyable. Ma partie préférée est quand Herb Sutter lève ses 5 doigts et dit: "C'est combien de personnes peuvent vraiment et correctement travailler avec ceux-ci."Cela devrait vous donner un indice de la complexité. Néanmoins, il existe des exemples triviaux faciles à saisir (comme un compteur mis à jour par plusieurs threads qui ne se soucie pas des autres garanties de mémoire, mais se soucie seulement qu'il est lui-même incrémenté correctement).

Un autre exemple est quand (en java) vous voulez un indicateur volatile pour contrôler les threads pour arrêter/démarrer. Vous savez, le classique:

volatile boolean stop = false; // on thread writes, one thread reads this    

Si vous travaillez avec java, vous savez que sans volatile ce code est cassé (vous pouvez lire pourquoi doubler vérifier le verrouillage est cassé, sans elle, par exemple). Mais savez-vous aussi que pour certaines personnes qui écrivent du code haute performance, c'est trop? volatile lecture/écriture garantit également séquentiel cohérence - cela a de fortes garanties et certaines personnes veulent une version plus faible de cela.

Un drapeau thread safe, mais pas volatile? Oui, exactement: VarHandle::set/getOpaque.

Et vous vous demanderiez pourquoi quelqu'un pourrait avoir besoin de cela par exemple? Tout le monde n'est pas intéressé par tous les changements qui sont soutenus par un volatile.

Voyons comment nous allons y parvenir en java. Tout d'abord, de telles choses exotiques existaient déjà dans l'API: AtomicInteger::lazySet. Ceci n'est pas spécifié dans le modèle de mémoire Java et n'a pas de définition claire; encore des gens l'ont utilisé (LMAX, afaik ou ceci pour plus de lecture). À mon humble avis, AtomicInteger::lazySet est VarHandle::releaseFence (ou VarHandle::storeStoreFence).


Nous allons essayer de répondre pourquoi quelqu'un a besoin de ces?

JMM a essentiellement deux façons d'accéder à un champ: plaine et volatils (ce qui garantit séquentielle cohérence). Toutes ces méthodes que vous mentionnez sont là pour apporter quelque chose entre ces deux - libérer/acquérir la sémantique; il y a des cas, je suppose, où les gens en fait en ont besoin.

Un même plus relaxation à partir de presse/acquérir est opaque, qui je suis encore à essayer de comprendre pleinement les.


Donc bottom line (votre compréhension est assez correcte, btw): si vous prévoyez de l'utiliser en java - ils n'ont pas de spécification pour le moment, faites-le à vos risques et périls. Si vous ne voulez pour les comprendre, leurs modes équivalents C++ sont le point de départ.

 13
Author: Eugene, 2020-02-09 18:15:58