Concurrence Java: CAS vs locking

Je lis le livre Java Concurrency en pratique . Au chapitre 15, ils parlent des algorithmes non bloquants et de la méthode de comparaison et échange .

Il est écrit que CAS effectue beaucoup mieux que les méthodes de locking. Je veux demander aux personnes qui ont déjà travaillé avec ces deux concepts et aimerais entendre quand vous préférez lequel de ces concepts? Est-ce vraiment beaucoup plus rapide?

Pour moi, l’utilisation des serrures est beaucoup plus claire et plus facile à comprendre et peut-être même meilleure à maintenir (corrigez-moi si je me trompe) . Devrions-nous nous concentrer sur la création de notre code concurrent lié à CAS plutôt que sur les verrous pour obtenir un meilleur gain de performance ou est-ce que la durabilité est plus importante?

Je sais qu’il n’y a peut-être pas une règle ssortingcte pour savoir quoi utiliser. 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 locking, mais cela dépend du degré de conflit. Comme CAS peut forcer une nouvelle tentative si la valeur change entre la lecture et la comparaison, un thread peut théoriquement restr bloqué dans une attente-occupée si la variable en question est touchée par de nombreux autres threads (ou si le calcul d’une nouvelle valeur coûte cher) de l’ancienne valeur (ou les deux)).

Le principal problème avec CAS est qu’il est beaucoup plus difficile de programmer correctement que de verrouiller. Rappelez-vous que le locking est à son tour beaucoup plus difficile à utiliser correctement que le passage de messages ou le STM . Ne ​​prenez donc pas cela comme un endossement valable pour l’utilisation de verrous.

La vitesse relative des opérations est en grande partie une question non résolue. Ce qui est pertinent, c’est la différence d’évolutivité entre les algorithmes basés sur les verrous et les algorithmes 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 des verrous.

Vous pouvez regarder les nombres entre un ConcurrentLinkedQueue et un BlockingQueue . Ce que vous verrez, c’est que CAS est nettement plus rapide dans les conflits de threads modérés (plus réalistes dans les applications réelles).

La propriété la plus intéressante des algorithmes non bloquants est le fait que si un thread échoue (échec du cache, ou pire, erreur de segmentation), les autres threads ne remarqueront pas cet échec et pourront continuer. Cependant, lors de l’acquisition d’un verrou, si le thread de blocage possède une sorte de défaillance du système d’exploitation, chaque autre thread en attente de la libération du verrou sera également touché par l’échec.

Pour répondre à vos questions, oui, les algorithmes ou collections non bloquants ( ConcurrentLinkedQueue , ConcurrentSkipListMap/Set ) peuvent être beaucoup plus rapides que leurs homologues bloquants. Comme Marcelo l’a souligné, il est très difficile d’obtenir des algorithmes non bloquants et cela demande beaucoup de considération.

Vous devriez en savoir plus sur les files d’attente Michael et Scott , c’est l’implémentation de la queue pour ConcurrentLinkedQueue et explique comment gérer une fonction atomique bidirectionnelle, sécurisée pour les threads, avec un seul CAS .

Il y a un bon livre fortement lié au sujet de la simultanéité sans locking: “L’art de la programmation multiprocesseur” par Maurice Herlihy

Si vous cherchez une comparaison du monde réel, 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 génère des statistiques.

Le thread n ° 1 échange un seul paquet à la fois avec le thread n ° 2

Résultat n ° 1 – utilise un échange basé sur CAS personnalisé utilisant les mêmes principes que SynchronousQueue , où notre classe s’appelle 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 n ° 2 – Lorsque nous remplaçons notre implémentation CAS avec le standard 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 pourrait pas être plus claire.