web-dev-qa-db-fra.com

Fonctionnement de Compare and Swap

J'ai lu pas mal de messages qui disent que comparer et échanger garantit l'atomicité, mais je ne suis toujours pas en mesure de savoir comment. Voici un pseudo-code général pour comparer et échanger:

int CAS(int *ptr,int oldvalue,int newvalue)
{
   int temp = *ptr;
   if(*ptr == oldvalue)
       *ptr = newvalue
   return temp;
}

Comment cela garantit-il l'atomicité? Par exemple, si j'utilise ceci pour implémenter un mutex,

void lock(int *mutex)
{  
    while(!CAS(mutex, 0 , 1));
}

comment cela empêche-t-il 2 threads d'acquérir le mutex en même temps? Tout pointeur serait vraiment apprécié.

19
StackSmasher

Le "pseudo code général" n'est pas un code réel d'implémentation CAS (comparer et échanger). Des instructions matérielles spéciales sont utilisées pour activer du matériel atomique spécial dans le CPU. Par exemple, en x86, le LOCK CMPXCHG Peut être utilisé ( http://en.wikipedia.org/wiki/Compare-and-swap ).

Dans gcc, par exemple, il y a __sync_val_compare_and_swap() builtin - qui implémente le CAS atomique spécifique au matériel. Il y a une description de cette opération dans un livre merveilleux et frais de Paul E. McKenney ( La programmation parallèle est-elle difficile, et si oui, que pouvez-vous faire à ce sujet? , 2014), section 4.3 "Opérations atomiques" , pages 31-32.

Si vous voulez en savoir plus sur la création d'une synchronisation de niveau supérieur au-dessus des opérations atomiques et sauver votre système des verrous tournants et de la gravure des cycles de processeur sur la rotation active, vous pouvez lire quelque chose sur le mécanisme futex sous Linux. Le premier article sur les futex est les futex sont difficiles par Ulrich Drepper 2011; l'autre est l'article LWN http://lwn.net/Articles/360699/ (et l'historique est Fuss, Futexes and Furwocks: Fast Userland Locking in Linux , 2002 )

Les verrous Mutex décrits par Ulrich n'utilisent que des opérations atomiques pour le "chemin rapide" (lorsque le mutex n'est pas verrouillé et que notre thread est le seul à vouloir le verrouiller), mais si le mutex était verrouillé, le thread se mettra en veille à l'aide de futex ( FUTEX_WAIT ...) (et il marquera la variable mutex en utilisant l'opération atomique, pour informer le thread de déverrouillage sur "il y a quelqu'un qui dort en attente sur ce mutex", donc le unlocker saura qu'il doit les réveiller en utilisant futex (FUTEX_WAKE, .. .)

23
osgx

Comment empêche-t-il deux threads d'acquérir le verrou? Eh bien, une fois qu'un thread réussit, *mutex sera 1, donc le CAS de tout autre thread échouera (car il est appelé avec la valeur attendue 0). Le verrou est libéré en stockant 0 dans *mutex.

Notez que c'est une utilisation étrange de CAS, car c'est essentiellement nécessitant une violation ABA. En règle générale, vous utiliseriez simplement un échange atomique simple:

while (exchange(mutex, 1) == 1) { /* spin */ }

// critical section

*mutex = 0;   // atomically

Ou si vous voulez être un peu plus sophistiqué et stocker des informations sur le thread qui a le verrou, vous pouvez faire des tours avec atomic-fetch-and-add (voir par exemple le code spinlock du noyau Linux).

4
Kerrek SB