Je lis le livre Concurrence Java en pratique . Dans le chapitre 15, ils parlent des algorithmes non bloquants et de la méthode de comparaison et d'échange (CAS).
Il est écrit que CAS fonctionne bien mieux que les méthodes de verrouillage. Je veux demander aux personnes qui ont déjà travaillé avec ces deux concepts et qui aimeraient savoir quand vous préférez lequel de ces concepts? Est-ce vraiment beaucoup plus rapide?
Pour moi, l'utilisation des verrous est beaucoup plus claire et plus facile à comprendre et peut-être encore meilleure à entretenir (veuillez me corriger si je me trompe) . Devrions-nous vraiment nous concentrer sur la création de notre code concurrent lié à CAS que les verrous pour obtenir une meilleure amélioration des performances ou la durabilité est-elle plus importante?
Je sais qu'il n'y a peut-être pas de règle stricte quand utiliser quoi. Mais je voudrais juste entendre quelques opinions, expériences avec le nouveau concept de CAS.
CAS est généralement beaucoup plus rapide que le verrouillage, mais cela dépend du degré de contention. Parce que CAS peut forcer une nouvelle tentative si la valeur change entre la lecture et la comparaison, un thread peut théoriquement se coincer dans une attente occupée si la variable en question est durement touchée par de nombreux autres threads (ou s'il est coûteux de calculer une nouvelle valeur de l'ancienne valeur (ou les deux)).
Le principal problème avec CAS est qu'il est beaucoup plus difficile de programmer correctement qu'avec le verrouillage. Attention, le verrouillage est, à son tour, beaucoup plus difficile à utiliser correctement que la transmission de messages ou STM , alors ne prenez pas cela comme une approbation de sonnerie pour le utilisation de serrures.
La vitesse relative des opérations est en grande partie un problème. Ce qui est pertinent, c'est la différence d'évolutivité entre les algorithmes basés sur les verrous et les non bloquants. Et si vous utilisez un système à 1 ou 2 cœurs, arrêtez de penser à de telles choses.
Les algorithmes non bloquants évoluent généralement mieux car ils ont des "sections critiques" plus courtes que les algorithmes basés sur les verrous.
Vous pouvez regarder les nombres entre un ConcurrentLinkedQueue
et un BlockingQueue
. Ce que vous verrez, c'est que [~ # ~] cas [~ # ~] est sensiblement plus rapide dans les conflits de threads modérés (plus réalistes dans les applications réelles).
La propriété la plus attrayante des algorithmes non bloquants est le fait que si un thread échoue (cache cache, ou pire, faute de segmentation), les autres threads ne le remarqueront pas. échec et peut continuer. Cependant, lors de l'acquisition d'un verrou, si le thread contenant le verrou a une sorte de défaillance du système d'exploitation, tous les autres threads en attente de libération du verrou seront également touchés par la défaillance.
Pour répondre à vos questions, oui, des algorithmes ou des collections thread-safe non bloquants (ConcurrentLinkedQueue
, ConcurrentSkipListMap/Set
) peut être beaucoup plus rapide que leurs homologues bloquants. Comme Marcelo l'a souligné cependant, obtenir des algorithmes non bloquants corrects est très difficile et nécessite beaucoup de considération.
Vous devriez lire à propos de Michael et Scott Queue , il s'agit de l'implémentation de la file d'attente pour ConcurrentLinkedQueue
et explique comment gérer une fonction atomique bidirectionnelle, thread-safe, avec un seul [~ # ~] cas [~ # ~].
Il y a un bon livre fortement lié au sujet de la concurrence sans verrouillage: "L'art de la programmation multiprocesseur" par Maurice Herlihy
Si vous cherchez une comparaison réelle, en voici une. Notre application a deux (2) threads 1) un thread de lecture pour la capture de paquets réseau et 2) un thread consommateur qui prend le paquet, le compte et rapporte des statistiques.
Le thread # 1 échange un seul paquet à la fois avec le thread # 2
Résultat # 1 - utilise un échange basé sur CAS personnalisé utilisant les mêmes principes que SynchronousQueue , où notre classe est appelée CASSynchronousQueue :
30,766,538 packets in 59.999 seconds :: 500.763Kpps, 1.115Gbps 0 drops
libpcap statistics: recv=61,251,128, drop=0(0.0%), ifdrop=0
Résultat # 2 - lorsque nous remplaçons notre implémentation CAS par la norme Java SynchronousQueue :
8,782,647 packets in 59.999 seconds :: 142.950Kpps, 324.957Mbps 0 drops
libpcap statistics: recv=69,955,666, drop=52,369,516(74.9%), ifdrop=0
Je ne pense pas que la différence de performance ne puisse être plus claire.