(Interessant, je parles de ca a mes etudiants en classe en ce moment)
Il y a plusieurs facon de synchoniser deux threads (logique). Les methodes de synchronisation du systeme d'exploitation (typiquement les mutex, semaphores et compagnie) sont des methodes de synchronization qui repose sur une semantique propre au systeme. Tu prends un mutex, pour faire ca, tu fais un appel de fonction qui te fait tomber en espace noyau. De la le noyau assure que la semantique du mutex est respecte. C'est a dire que si un autre thread a ce mutex, alors le premier thread doit attendre, c'est a dire qu'il est deschedule par le systeme jusqu'a ce que le mutex soit libre. La synchronisation se fait au niveau du systeme et typiquement coute cher: probablement au moins un contexte switch (* voir futex plus bas). En plus en fonction du comportement et du scheduling des autres threads, il peut se retrouver a attendre plus longtemps.
La primitive fetch_and_add dont tu parles fait partie des operations atomique du processeur comme les test_and_set et compare_and_swap et les autres trucs du genre (ca depend des processeurs). Basiquement c'est operations ne sont pas des operations faites par le systeme, mais par ton processeur. Elles fonctions en lockant physiquement une ligne de cache de la memoire principale. Ainsi, le cout typique de cette operation est tres court, beaucoup plus court qu'un appel systeme. Aussi cette primitive n'est pas bloquante.
Ces primitives atomiques peuvent avoir de nombreuses utilite, la plus courante etant l'implementation de spinlock qui essentiellement sont une primitive de synchronisation comme un mutex, sauf qu'elle est deploye en espace utilisateur entierement. Ca permet d'avoir une synchronisation potentiellement beaucoup plus rapide, mais ca conserve un thread (logique) active ce qui consomme les ressources d'un thread (physique) a temps plein. Les systeme implementes maintenant des mutex hybride, appelle futex sous linux (et j'en sais rien dans les autres systemes) qui sont un genre d'hybride des spinlock et des mutex, basiquement, tu fais du spinlock pendant 50ms et apres tu switch sur un mutex.
Mais l'interet principal de ces operations atomiques n'est pas dans la reimplementation d'un verrou parcequ'il est toujours succeptible a des problemes d'attente sur un verrou. Imagine que tu ais un code comme celui ci: "mutexlock(); i++; mutexunlock();" tu pourrais avoir envie de dire que comme ce que le code protege par le mutex est tres court et que donc, on s'en fout en terme de performance. Le probleme est que si ton thread (logique) est deschedule par le systeme alors qu'il tient le mutex, alors potentiellement TOUS les autres threads (logique) de ton systeme peuvent se retrouver a attendre jusqu'a ce que le thread (logique) qui tient le mutex soit schedule a nouveau.
Ici, tu peux faire la meme operation en utilisant un unique fetch_and_add sur la variable i. Comme c'est une operation user space, le temps d'attente est naturellement tres faible (le cout reel d'u atomique est complique a calculer parcequ'il depend de la saturation du bus memoire et du bus reliant les different cache et servant a leur synchronisation.) Et ainsi cette operation ne provoquera jamais une attente considerable des autres threads (logique).
Ces operations peuvent etre utilise pour implementer de nombreuse structures de donne parallel ou structure de synchronisation qui arrive a garantir que aucun verrou n'est jamais pris. Si tu veux en savoir plus. Regardes les concepts de lock-free et wait-free data structures. Si tu cherches des articles en particulier, il y en a de nombreux publie dans la conference IPDPS.
Si tu as d'autres questions ou veut plus d'explication n'hesites pas a demander.