web-dev-qa-db-fra.com

Qu'est-ce que la concurrence "non bloquante" et en quoi est-elle différente de la concurrence normale?

  1. Qu'est-ce que la concurrence "non bloquante" et en quoi est-elle différente de la concurrence normale utilisant des threads? Pourquoi n'utilisons-nous pas la concurrence non bloquante dans tous les scénarios où la concurrence est requise? Y a-t-il des frais généraux pour l'utilisation de la concurrence non bloquante?
  2. J'ai entendu dire que la concurrence non bloquante est disponible en Java. Existe-t-il des scénarios particuliers dans lesquels nous devrions utiliser cette fonctionnalité?
  3. Y a-t-il une différence ou un avantage à utiliser l'une de ces méthodes avec une collection? Quels sont les compromis?

Exemple pour Q3:

class List   
{  
    private final ArrayList<String> list = new ArrayList<String>();

    void add(String newValue) 
    {
        synchronized (list)
        {
            list.add(newValue);
        }
    }
}  

vs.

private final ArrayList<String> list = Collections.synchronizedList(); 

Les questions sont plus d'un point de vue apprentissage/compréhension. Merci pour l'attention.

39
codeObserver

Qu'est-ce que la concurrence non bloquante et en quoi est-elle différente?.

Formel:

En informatique, la synchronisation non bloquante garantit que les threads en compétition pour une ressource partagée ne voient pas leur exécution indéfiniment retardée par exclusion mutuelle. Un algorithme non bloquant est sans verrouillage s'il y a une progression garantie à l'échelle du système; sans attente s'il y a également une progression garantie par thread. (Wikipédia)

Informel: L'une des caractéristiques les plus avantageuses du non-blocage par rapport au blocage est que les threads n'ont pas à être suspendus/réactivés par le système d'exploitation. Une telle surcharge peut aller de 1 ms à quelques 10 ms, donc la suppression de cela peut être un gros gain de performances. En Java, cela signifie également que vous pouvez choisir d'utiliser un verrouillage non équitable, qui peut avoir un débit système beaucoup plus important que le verrouillage équitable.

J'ai entendu dire que cela est disponible en Java. Y a-t-il des scénarios particuliers que nous devrions utiliser cette fonctionnalité

Oui, depuis Java5. En fait, en Java vous devriez essentiellement essayer de répondre à vos besoins avec Java.util.concurrent autant que possible (ce qui arrive à utiliser beaucoup de concurrence non bloquante, mais vous n'avez pas dans la plupart des cas). Seulement si vous n'avez pas d'autre option, vous devez utiliser des wrappers synchronisés (.synchronizedList () etc.) ou un mot clé manuel synchronize. De cette façon, vous vous retrouvez la plupart du temps avec des applications plus maintenables et plus performantes.

La concurrence non bloquante est particulièrement avantageuse lorsqu'il y a beaucoup de conflits. Vous ne pouvez pas l'utiliser lorsque vous avez besoin de blocage (verrouillage équitable, éléments déclenchés par les événements, file d'attente avec une longueur maximale, etc.), mais si vous n'en avez pas besoin, la concurrence non bloquante a tendance à mieux fonctionner dans la plupart des conditions.

Y a-t-il une différence/un avantage à utiliser l'une de ces méthodes pour une collection. Quels sont les compromis

Les deux ont le même comportement (le code d'octet doit être égal). Mais je suggère d'utiliser Collections.synchronized parce que c'est plus court = plus petite pièce à foutre!

20
Enno Shioji

Qu'est-ce que la concurrence non bloquante et en quoi est-elle différente de la concurrence normale utilisant des threads.

La concurrence non bloquante est une manière différente de coordonner l'accès entre les threads du blocage de la concurrence. Il existe de nombreux éléments de base (théoriques), mais l'explication la plus simple (car il semble que vous cherchiez une réponse simple et pratique) est que la concurrence non bloquante n'utilise pas de verrous.

Pourquoi ne pas utiliser la simultanéité "non bloquante" dans tous les scénarios où la simultanéité est requise.

Nous faisons. Je vais vous montrer un peu. Mais il est vrai qu'il n'y a pas toujours d'algorithmes non bloquants efficaces pour chaque problème de concurrence.

y a-t-il des frais généraux pour "non bloquant"

Eh bien, il y a des frais généraux pour tout type de partage d'informations entre les threads qui vont jusqu'à la structure du CPU, en particulier lorsque vous obtenez ce que nous appelons "contention", c'est-à-dire la synchronisation de plusieurs threads qui tentent d'écrire sur le même emplacement mémoire en même temps. Mais en général, le non-blocage est plus rapide que le blocage (basé sur un verrouillage) dans de nombreux cas, en particulier dans tous les cas où il existe une implémentation simple, sans verrouillage et bien connue d'un algorithme/structure de données donné. Ce sont ces bonnes solutions qui sont fournies avec Java.

J'ai entendu dire que cela est disponible en Java.

Absolument. Pour commencer, toutes les classes de Java.util.concurrent.atomic fournissent une maintenance sans verrouillage des variables partagées. De plus, toutes les classes de Java.util.concurrent dont les noms commencent par ConcurrentLinked ou ConcurrentSkipList, fournissent une implémentation sans verrouillage des listes, des mappes et des ensembles.

Y a-t-il des scénarios particuliers que nous devrions utiliser cette fonctionnalité.

Vous voudrez utiliser la file d'attente sans verrou et déquez dans tous les cas où vous utiliseriez autrement (avant JDK 1.5) Collections.synchronizedlist, car ils offrent de meilleures performances dans la plupart des conditions. c'est-à-dire que vous les utiliseriez chaque fois que plusieurs threads modifient simultanément la collection, ou lorsqu'un thread modifie la collection et que d'autres threads tentent de la lire. Notez que le très populaire ConcurrentHashMap utilise en fait des verrous en interne, mais il est plus populaire que ConcurrentSkipListMap car je pense qu'il offre de meilleures performances dans la plupart des scénarios. Cependant, je pense que Java 8 inclurait une implémentation sans verrouillage de ConcurrentHashMap.

Y a-t-il une différence/un avantage à utiliser l'une de ces méthodes pour une collection. Quels sont les compromis

Eh bien, dans ce court exemple, ils sont exactement les mêmes. Notez cependant que lorsque vous avez des lecteurs et des écrivains simultanés, vous devez synchroniser les lectures ainsi que les écritures, et Collections.synchronizedList () le fait. Vous voudrez peut-être essayer le ConcurrentLinkedQueue sans verrou comme alternative. Cela peut vous donner de meilleures performances dans certains scénarios.

Remarque générale

Bien que la concurrence soit un sujet très important à apprendre, gardez à l'esprit qu'il s'agit également d'un sujet très délicat, où même les développeurs très expérimentés se trompent souvent. Pire encore, vous pourriez découvrir des bogues de concurrence uniquement lorsque votre système est sous forte charge. Je recommanderais donc toujours d'utiliser autant de classes et bibliothèques simultanées prêtes à l'emploi que de déployer les vôtres.

10
pron

1) Qu'est-ce que la concurrence non bloquante et en quoi est-elle différente?.

Une tâche (thread) n'est pas bloquante lorsqu'elle ne fait pas attendre d'autres tâches (threads) jusqu'à ce que la tâche soit terminée.

2) J'ai entendu dire que cela est disponible en Java. Y a-t-il des scénarios particuliers que nous devrions utiliser cette fonctionnalité

Java prend en charge son propre multithreading. Vous pouvez en profiter pour exécuter plusieurs tâches simultanément. Une fois bien écrit et implémenté, cela peut accélérer le programme lors de l'exécution sur une machine avec plusieurs CPU logiques. Pour commencer, il existe Java.lang.Runnable et Java.lang.Thread en tant qu'implémentations concurrentes de bas niveau. Ensuite, il existe une concurrence de haut niveau dans la saveur de l'API Java.util.concurrent .

3) Y a-t-il une différence/un avantage à utiliser l'une de ces méthodes pour une collection. Quels sont les compromis

J'utiliserais Collections#synchronizedList() , non seulement parce que c'est plus robuste et pratique, mais aussi parce qu'un Map n'est pas un List. Il n'est pas nécessaire d'essayer d'en cultiver une lorsque l'API offre déjà la possibilité.


Cela dit, il y a un tutoriel Sun sur la concurrence . Je vous recommande de vous en sortir.

10
BalusC

1] Qu'est-ce que la concurrence non bloquante et en quoi est-elle différente.

Comme d'autres l'ont mentionné, le non-blocage est une façon de dire sans blocage (ce qui signifie que nous ne devrions pas avoir une condition où la progression s'arrête complètement pendant que les threads sont bloqués, en attente d'accès).

Ce que l'on entend par "simultanéité", c'est simplement que plusieurs calculs se produisent en même temps (simultanément).

2) J'ai entendu dire que cela est disponible en Java. Y a-t-il des scénarios particuliers que nous devrions utiliser cette fonctionnalité

Vous souhaitez utiliser des algorithmes non bloquants lorsqu'il est important que plusieurs threads puissent accéder simultanément aux mêmes ressources, mais nous ne nous soucions pas autant de l'ordre d'accès ou des ramifications possibles de l'action d'entrelacement (plus d'informations ci-dessous).

] Y a-t-il une différence/un avantage à utiliser une de ces méthodes pour une collection. Quels sont les compromis

.

L'utilisation du bloc synchronisé (liste) garantit que toutes les actions effectuées dans le bloc sont considérées comme atomiques. Autrement dit, tant que nous n'accédons à la liste qu'à partir de blocs synchronisés (liste), toutes les mises à jour de la liste apparaîtront comme si elles se produisaient en même temps dans le bloc.

Un objet synchronizedList (ou synchronizedMap) garantit uniquement que les opérations individuelles sont thread-safe. Cela signifie que deux insertions ne se produiront pas simultanément. Considérez la boucle suivante:

for(int i=0; i < 4; i++){
    list.add(Integer.toString(i));
}

Si la liste utilisée était un synchronizedList et que cette boucle a été exécutée sur deux threads différents, alors nous pouvons nous retrouver avec {0,0,1,2,1,3,2,3} dans notre liste, ou une autre permutation.

Pourquoi? Eh bien, nous sommes garantis que le thread 1 ajoutera 0-3 dans cet ordre et nous sommes garantis de même pour le thread 2, mais nous n'avons aucune garantie de la façon dont ils s'entrelaceront.

Si, cependant, nous avons enveloppé cette liste dans un bloc (liste) synchronisé:

synchronized(list){
    for(int i=0; i < 4; i++){
        list.add(Integer.toString(i));
    }
}

Nous sommes garantis que les inserts du fil 1 et du fil 2 ne s'entrelaceront pas, mais ils se produiront d'un coup. Notre liste contiendra {0,1,2,3,0,1,2,3}. L'autre thread se bloquera, en attente sur la liste, jusqu'à ce que le premier thread se termine. Nous n'avons aucune garantie quel fil sera le premier, mais nous avons la garantie qu'il se terminera avant que l'autre ne commence.

Ainsi, certains compromis sont les suivants:

  • Avec une syncrhonizedList, vous pouvez insérer sans utiliser explicitement un bloc synchronisé.
  • Un syncrhonizedList peut vous donner un faux sentiment de sécurité, car vous pouvez naïvement croire que les ouvertures successives sur un thread sont atomiques, alors que seules les opérations individuelles sont atomiques
  • L'utilisation d'un bloc synchronisé (liste) doit être effectuée avec précaution, car nous sommes en mesure de créer un blocage (voir ci-dessous).

Nous pouvons créer un blocage lorsque deux threads (ou plus) attendent chacun un sous-ensemble de ressources détenues par un autre. Si, par exemple, vous aviez deux listes: userList et movieList.

Si le thread 1 acquiert d'abord le verrou sur userList, puis movieList, mais que le thread deux exécute ces étapes à l'envers (acquiert le verrou sur movieList avant userList), alors nous nous sommes ouverts pour l'impasse. Considérez le cours des événements suivant:

  1. Le thread 1 verrouille la liste d'utilisateurs
  2. Le thread 2 se verrouille sur MovieList
  3. Le thread 1 essaie de verrouiller la liste de films, attend le thread 2 pour sortir
  4. Le thread 2 essaie de verrouiller la liste d'utilisateurs, attend le thread 1 pour se libérer

Les deux threads attendent l'autre et aucun ne peut avancer. Il s'agit d'un scénario de blocage, et comme aucun ne renoncera à ses ressources, nous sommes dans l'impasse.

4
Ed Gonzalez

1) La synchronisation non bloquante signifie que le risque de blocages est supprimé. Aucun thread n'attendra pour obtenir un verrou détenu par un autre thread "pour toujours".

2) Plus d'informations sur la synchronisation non bloquante algorithmes en Java.

3
Lars Andren
  1. Wikipédia est une excellente ressource pour tout étudiant en informatique. Voici un article sur la synchronisation non bloquante - http://en.wikipedia.org/wiki/Non-blocking_synchronization

  2. La synchronisation non bloquante est disponible dans n'importe quelle langue, c'est ainsi que le programmeur définit son application multi-thread.

  3. Vous ne devez utiliser le verrouillage (c'est-à-dire synchronisé (liste)) que lorsque cela est nécessaire en raison du temps nécessaire pour acquérir le verrou. En Java, le Vector est une structure de données thread-safe qui est très similaire à ArrayList de Java.

2
Eric

1: Qu'est-ce que la concurrence "non bloquante" et en quoi est-elle différente de la concurrence normale utilisant des threads? Pourquoi n'utilisons-nous pas la concurrence non bloquante dans tous les scénarios où la concurrence est requise? Y a-t-il des frais généraux pour utiliser non -blocage simultané?

Les algorithmes non bloquants n'utilisent pas de schémas de verrouillage d'objet spécifiques pour contrôler l'accès simultané à la mémoire (les verrous d'objets synchronisés et standard sont des exemples qui utilisent des verrous de niveau objet/fonction pour réduire les problèmes d'accès simultané en Java. Au lieu de cela, ils utilisent une certaine forme d'instructions de bas niveau pour effectuer (à un certain niveau) une comparaison et un échange simultanés sur un emplacement de mémoire; si cela échoue, il renvoie simplement faux et ne génère pas d'erreur, si cela fonctionne, cela a réussi et vous passez à autre chose. En général, cela est tenté en boucle jusqu'à ce qu'il fonctionne, car il n'y aura que de petites périodes (espérons-le) où cela échouera, il boucle simplement quelques fois supplémentaires jusqu'à ce qu'il puisse définir la mémoire dont il a besoin.

Ceci n'est pas toujours utilisé car il est beaucoup plus complexe du point de vue du code, même pour des cas d'utilisation relativement triviaux que la synchronisation standard Java. De plus, pour la plupart des utilisations, l'impact sur les performances du verrouillage est trivial par rapport à à d'autres sources du système. Dans la plupart des cas, les exigences de performances ne sont pas assez élevées pour justifier même.

Enfin, à mesure que le JDK/JRE évolue, les concepteurs principaux améliorent les implémentations du langage interne pour tenter d'incorporer les moyens les plus efficaces d'atteindre ces objectifs dans les constructions de base. Au fur et à mesure que vous vous éloignez des constructions de base, vous perdez l'implémentation automatique de ces améliorations car vous utilisez moins d'implémentations standard (par exemple jaxb/jibx; jaxb utilisé pour sous-performer grossièrement jibx, mais est maintenant égal sinon plus rapide dans la plupart des cas que je 'ai testé à partir de Java 7) lorsque vous augmentez votre version Java.

si vous regardez l'exemple de code ci-dessous, vous pouvez voir les emplacements "généraux". Ce n'est pas vraiment une surcharge en soi, mais le code doit être extrêmement efficace pour fonctionner sans verrouillage et fonctionner réellement mieux qu'une version synchronisée standard en raison du bouclage. Même de légères modifications peuvent conduire à un code qui passera de plusieurs fois mieux que le standard à un code qui est plusieurs fois pire (par exemple, des instanciations d'objets qui n'ont pas besoin d'être là ou même des vérifications conditionnelles rapides; vous parlez de sauver des cycles ici, donc la différence entre le succès et l'échec est très mince).

2: J'ai entendu dire que la concurrence non bloquante est disponible en Java. Existe-t-il des scénarios particuliers où nous devrions utiliser cette fonctionnalité?

À mon avis, vous ne devriez l'utiliser que si vous A) avez un problème de performance éprouvé dans votre système en cours de production, sur son matériel de production; et B) si vous pouvez prouver que la seule inefficacité restante dans la section critique est liée au verrouillage; C) vous avez la ferme adhésion de vos parties prenantes qu'elles sont prêtes à avoir un code non standard moins maintenable en échange de l'amélioration des performances que vous devez D) prouver numériquement sur votre matériel de production pour être certain que cela aidera même du tout.

: Y a-t-il une différence ou un avantage à utiliser l'une de ces méthodes avec une collection? Quels sont les compromis?

L'avantage est la performance, le compromis est d'abord qu'il s'agit d'un code plus spécialisé (de nombreux développeurs ne savent pas quoi en faire; il est donc plus difficile pour une nouvelle équipe ou une nouvelle recrue de se mettre à jour, rappelez-vous que la majorité des le coût du logiciel est de la main-d'œuvre, vous devez donc surveiller le coût total de possession que vous imposez à travers les décisions de conception), et que toutes les modifications doivent être testées à nouveau pour garantir que la construction est toujours plus rapide. Généralement, dans un système qui nécessiterait cela, des tests de performances ou de charge et de débit seraient nécessaires pour toute modification. Si vous ne faites pas ces tests, je dirais que vous n'avez presque certainement pas besoin de penser à ces approches et que vous ne verriez presque certainement aucune valeur pour la complexité accrue (si vous réussissiez tout).

Encore une fois, je n'ai qu'à reformuler tous les avertissements standard contre l'optimisation en général, car bon nombre de ces arguments sont les mêmes que ceux que j'utiliserais contre cela comme conception. De nombreux inconvénients sont les mêmes que pour toute optimisation, par exemple, chaque fois que vous modifiez le code, vous devez vous assurer que votre `` correctif '' n'introduit pas l'inefficacité dans une construction qui n'a été placée que pour améliorer les performances et gérer cela (c'est-à-dire jusqu'à la refactorisation de la section entière pour supprimer potentiellement les optimisations) si le correctif est critique et qu'il réduit les performances.

Il est vraiment, vraiment facile de gâcher cela de manière très difficile à déboguer, donc si vous n'avez pas à le faire (ce que je n'ai trouvé que quelques scénarios où vous le feriez jamais; et pour moi, ceux-ci étaient assez discutables et j'aurais préféré ne pas le faire) ne le fais pas. utilisez les trucs standard et tout le monde sera plus heureux!

Discussion/Code

La simultanéité sans blocage ou sans verrouillage évite l'utilisation de verrous d'objets spécifiques pour contrôler l'accès à la mémoire partagée (comme les blocs synchronisés ou les verrous spécifiques). Il y a un avantage de performance lorsque la section de code n'est pas verrouillable; cependant, le code dans la boucle CAS (si c'est votre chemin, il existe d'autres méthodes en Java) doit être très, très efficace ou cela finira par vous coûter plus de performances que vous n'en gagnez.

Comme toutes les optimisations de performances, la complexité supplémentaire ne vaut pas l'effet pour la plupart des cas d'utilisation. Proprement écrit Java utilisant des constructions standard fonctionnera aussi bien sinon mieux que la plupart des optimisations (et permettra en fait à votre organisation de maintenir le logiciel plus facilement une fois que vous serez parti). À mon avis, cela ne fait que détecter dans les sections à très hautes performances avec des problèmes de performances éprouvés où le verrouillage est la seule source d'inefficacité. Si vous n'avez pas de problème de performance connu et quantifié, j'éviterais d'utiliser une technique comme celle-ci jusqu'à ce que vous ayez prouvé que le problème est en fait là à cause du verrouillage et ne pas faire à d'autres problèmes avec l'efficacité du code. Une fois que vous avez un problème de performance basé sur le verrouillage prouvé, je m'assurerais que vous avez un certain type de métrique en place pour vous assurer que ce type de configuration va réellement pour fonctionner plus rapidement pour vous que d'utiliser simplement la norme Java simultanéité.

L'implémentation que j'ai faite pour cette utilisation utilise les opérations CAS et la famille de variables Atomic. Ce code de base n'a jamais verrouillé ni soulevé d'erreurs pour moi dans ce cas d'utilisation (entrée et sortie d'échantillonnage aléatoire pour les tests hors ligne à partir d'un système de traduction à haut débit). Cela fonctionne essentiellement comme ceci:

Vous avez un objet qui est partagé entre les threads, et cela est déclaré en tant qu'AtomicXXX ou AtomicReference (pour la plupart des cas d'utilisation non triviaux, vous exécuterez avec la version AtomicReference).

lorsque la valeur/l'objet donné est référencé, vous le récupérez dans l'encapsuleur atomique, cela vous obtient une copie locale sur laquelle vous effectuez des modifications. À partir de là, vous utilisez un compareAndSwap comme condition d'une boucle while pour tenter de définir cet Atomic à partir de votre thread, si cela échoue, il renvoie false au lieu de se verrouiller. Cela itérera jusqu'à ce qu'il fonctionne (le code dans cette boucle doit être très efficace et simple).

Vous pouvez rechercher les opérations CAS pour voir comment elles fonctionnent, elles sont essentiellement destinées à être implémentées comme un jeu d'instructions unique avec une comparaison à la fin pour voir si la valeur correspond à ce que vous avez essayé de définir.

Si compareAndSwap échoue, vous récupérez votre objet à partir de l'encapsuleur atomique, effectuez à nouveau les modifications, puis essayez à nouveau de comparer et de permuter jusqu'à ce qu'il fonctionne. Il n'y a pas de verrou spécifique, vous essayez simplement de remettre l'objet en mémoire et s'il échoue, essayez à nouveau chaque fois que votre thread reprend le contrôle.

Le code pour cela est ci-dessous pour un cas simple avec une liste:

/* field declaration*/
//Note that I have an initialization block which ensures that the object in this
//reference is never null, this was required to remove null checks and ensure the CAS
//loop was efficient enough to improve performance in my use case
private AtomicReference<List<SampleRuleMessage>> specialSamplingRulesAtomic = new AtomicReference<List<SampleRuleMessage>>();


/*start of interesting code section*/

    List<SampleRuleMessage> list = specialSamplingRulesAtomic.get();
    list.add(message);
    while(!specialSamplingRulesAtomic.compareAndSet(specialSamplingRulesAtomic.get(), list)){
        list = specialSamplingRulesAtomic.get();
        list.add(message);
    };
 /* end of interesting code section*/
2
Scott Taylor

La synchronisation non bloquante est la même qu'une synchronisation bloquante, les deux sont une sorte de synchronisation, la seule différence est que la synchronisation non bloquante est globalement plus rapide.

Pour commencer, vous souhaitez utiliser la synchronisation uniquement lorsque plusieurs threads accèdent à la même ressource dans la RAM. Vous ne pouvez pas utiliser la synchronisation lorsque vous essayez d'accéder à des choses sur le disque, ou mieux, vous devez utiliser des verrous sur le disque.

Cela dit, comment pouvez-vous synchroniser si aucun thread ne bloque jamais?

La réponse est un verrouillage optimiste. Cette idée existe depuis au moins 20 ans. Peut-être plus.

Vous avez peut-être entendu parler du langage LISP. Comme il s'avère que les langages fonctionnels ne modifient jamais ses paramètres, ne renvoient que de nouvelles valeurs, donc ils n'ont jamais besoin de se synchroniser.

Dans LISP, vous pouvez avoir un état partagé, mais cela devient délicat. La plupart des programmes peuvent donc fonctionner en parallèle et ne se soucient jamais de la synchronisation.

L'idée d'un verrouillage optimiste est que tous les threads modifient volontairement les valeurs partagées, mais ils ont une zone locale pour modifier les valeurs et n'appliquent la modification qu'à la fin, avec une instruction, atomiquement, en utilisant CAS. Cas signifie Compare And Swap, il fonctionne en un seul cycle de processeur et est implémenté dans les processeurs depuis au moins 20 ans.

Une excellente explication de ce qu'est le CAS: https://www.cs.umd.edu/class/fall2010/cmsc433/lectures/nonBlocking.pdf

Donc s'il y a un conflit dans la modification, cela n'affectera qu'un seul des auteurs, le reste sera fait avec.

De plus, s'il n'y a aucun conflit, les algorithmes non bloquants fonctionnent beaucoup plus rapidement que leurs homologues bloquants.

Tutoriel sur les algorithmes non bloquants en Java avec des exemples de code que vous pouvez utiliser dans la vie réelle: http://tutorials.jenkov.com/Java-concurrency/non-blocking-algorithms .html

1
bill schwarz