Question

J'essaie d'apprendre Clojure à partir de l'API et de la documentation disponible sur le site. Je ne connais pas très bien le stockage mutable dans Clojure et je veux m'assurer que ma compréhension est correcte. Faites-moi savoir s'il y a des idées avec lesquelles je me suis trompé.

Éditer: je suis en train de mettre à jour ceci car je reçois des commentaires sur son exactitude.

Avertissement: Toutes ces informations sont informelles et potentiellement fausses. N'utilisez pas ce message pour mieux comprendre le fonctionnement de Clojure.

Vars contiennent toujours une liaison racine et éventuellement une liaison par thread. Elles sont comparables aux variables habituelles dans les langages impératifs et ne conviennent pas au partage d'informations entre les threads. (merci Arthur Ulfeldt)

Les

références sont des emplacements partagés par des threads prenant en charge des transactions atomiques pouvant modifier l'état d'un nombre quelconque de références dans une seule transaction. Les transactions sont validées à la sortie des expressions de synchronisation (dosync) et les conflits sont résolus automatiquement avec la magie STM (annulations, files d'attente, attentes, etc.)

Les

agents permettent de partager des informations de manière asynchrone entre les threads avec un temps système minimal, en envoyant des fonctions d'action indépendantes pour modifier l'état de l'agent. Les agents sont renvoyés immédiatement et sont donc non bloquants, bien que la valeur d’un agent ne soit définie tant que la fonction distribuée n’est pas terminée.

Les

atomes sont des emplacements pouvant être partagés de manière synchrone entre les threads. Ils supportent la manipulation sécurisée entre différents threads.

Voici mon résumé convivial basé sur le moment d'utilisation de ces structures:

  • Vars ressemblent aux anciennes variables classiques dans les langages impératifs. (éviter si possible)
  • Les atomes ressemblent à Vars mais avec une sécurité de partage de threads qui permet une lecture immédiate et un réglage sûr. (merci Martin)
  • Un agent est comme un atome mais, au lieu de le bloquer, un nouveau thread est créé pour calculer sa valeur. Il ne bloque que s'il est en train de modifier une valeur et peut informer les autres threads que l'attribution est terminée.
  • Les références sont des emplacements partagés qui se verrouillent dans les transactions. Au lieu de laisser le programmeur décider de ce qui se passe pendant les conditions de concurrence pour chaque élément de code verrouillé, nous commençons simplement une transaction et laissons Clojure gérer toutes les conditions de verrouillage entre les références de cette transaction.

Un autre concept associé est la fonction future . Pour moi, il me semble qu'un futur objet peut être décrit comme un agent synchrone dans lequel il est impossible d'accéder à la valeur tant que le calcul n'est pas terminé. Il peut également être décrit comme un atome non bloquant. Ces conceptions du futur sont-elles précises?

Était-ce utile?

La solution

On dirait que vous obtenez vraiment Clojure! bon travail:)

Vars a une "liaison de racine" visible dans tous les threads et chaque thread individuel peut changer la valeur qu'il voit sans affecter les autres threads. Si ma compréhension est correcte, une var ne peut pas exister dans un seul thread avec une liaison racine visible de tous et ne pouvant pas être "rebound". jusqu'à ce qu'il soit défini avec (def ...) la première fois.

Les références sont validées à la fin de la transaction (dosync ...) qui inclut les modifications, mais uniquement lorsque la transaction a pu se terminer dans un état cohérent.

Autres conseils

Je pense que votre conclusion sur Atoms est fausse:

  

Les atomes sont comme Vars mais avec une sécurité de partage de threads qui bloque jusqu'à ce que la valeur ait changé

Les

atomes sont modifiés avec swap! ou de bas niveau avec compare-and-set! . Cela ne bloque jamais rien. swap! fonctionne comme une transaction avec une seule référence:

  1. l'ancienne valeur est extraite de l'atome et stockée dans le thread-local
  2. la fonction est appliquée à l'ancienne valeur pour générer une nouvelle valeur
  3. si cela réussit compare-and-set est appelé avec l'ancienne et la nouvelle valeur; si la valeur de l’atome n’a pas été modifiée par un autre thread (la valeur reste identique à l’ancienne), la nouvelle valeur est écrite, sinon l’opération reprend à (1) jusqu’à ce qu’elle réussisse.

J'ai trouvé deux problèmes avec votre question.

Vous dites:

  

Si vous accédez à un agent alors qu'une action est en cours, la valeur ne sera pas renvoyée tant que l'action ne sera pas terminée.

http://clojure.org/agents dit:

  

l'état d'un agent est toujours immédiatement disponible pour la lecture par n'importe quel thread

I.e. vous n'avez jamais à attendre pour obtenir la valeur d'un agent (je suppose que la valeur modifiée par une action est mandatée et modifiée de manière atomique).

Le code de la méthode deref d'un agent se présente comme suit (révision 1382 du SVN):

public Object deref() throws Exception{
    if(errors != null)
    {
        throw new Exception("Agent has errors", (Exception) RT.first(errors));
    }
return state;

}

Aucun blocage n'est impliqué.

De plus, je ne comprends pas ce que vous voulez dire (dans votre section Réf) par

  

Les transactions sont validées sur les appels à deref

Les transactions sont validées lorsque toutes les actions du bloc dosync sont terminées, qu'aucune exception n'a été lancée et que rien n'a entraîné une nouvelle tentative de transaction. Je pense que deref n’a rien à voir avec cela, mais j’ai peut-être mal compris votre propos.

Martin a raison d’affirmer que le fonctionnement de l’atome reprend à 1 heure. Cela s'appelle également rotation en attente. Bien que cela bloque réellement un verrou, le thread qui a exécuté l'opération est bloqué jusqu'à ce que l'opération réussisse. Il s'agit donc d'une opération de blocage et non d'une opération asynchrone.

Également à propos des contrats à terme, Clojure 1.1 a ajouté des abstractions pour les promesses et les contrats à terme. Une promesse est une construction de synchronisation qui peut être utilisée pour transmettre une valeur d'un thread à un autre. Tant que la valeur n'a pas été fournie, toute tentative de déréférencement de la promesse sera bloquée.

(def a-promise (promise))
(deliver a-promise :fred)

Les contrats à terme représentent des calculs asynchrones. Ils sont un moyen d’exécuter le code dans un autre thread et d’obtenir le résultat.

(def f (future (some-sexp)))
(deref f) ; blocks the thread that derefs f until value is available

Vars n’a pas toujours de liaison racine. Il est légal de créer une var sans lien à l'aide de

.
(def x)

ou

(declare x)

Tenter d'évaluer x avant qu'il ait une valeur aura pour résultat

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top