L'opération peut ne pas stocker réellement la nouvelle valeur dans la destination en raison d'une course avec un autre thread qui modifie la valeur au même moment que vous essayez. La primitive CAS ne garantit pas que l'écriture se produit - seulement que l'écriture se produit si la valeur est déjà ce qui est attendu. La primitive ne peut pas savoir quel est le comportement correct si la valeur n'est pas celle attendue, donc rien ne se produit dans ce cas - vous devez corriger le problème en vérifiant la valeur de retour pour voir si l'opération a fonctionné.
Ainsi, votre exemple:
elem->next = __sync_val_compare_and_swap(&head, head, elem); //always inserts?
insérera pas nécessairement le nouvel élément. Si un autre thread insère un élément au même moment, une condition de concurrence peut provoquer l'appel de ce thread à __sync_val_compare_and_swap()
pour ne pas mettre à jour head
(mais ni l'élément de ce thread ni celui de l'autre thread n'est perdu si vous le gérez correctement).
Mais, il y a un autre problème avec cette ligne de code - même si head
ne soient mis à jour, il y a un bref moment de temps où head
des points à l'élément inséré, mais le pointeur next
de cet élément n'a pas été mis à jour pour pointer vers le tête précédente de la liste. Si un autre fil s'infiltre pendant ce moment et essaie de marcher sur la liste, de mauvaises choses arrivent.
Pour mettre à jour correctement la liste changer cette ligne de code à quelque chose comme:
whatever_t* prev_head = NULL;
do {
elem->next = head; // set up `elem->head` so the list will still be linked
// correctly the instant the element is inserted
prev_head = __sync_val_compare_and_swap(&head, elem->next, elem);
} while (prev_head != elem->next);
Ou utiliser la variante bool
, que je pense est un peu plus pratique:
do {
elem->next = head; // set up `elem->head` so the list will still be linked
// correctly the instant the element is inserted
} while (!__sync_bool_compare_and_swap(&head, elem->next, elem));
Il est le genre de laid, et j'espère que je l'ai bien fait (il est facile de se trébucher dans les détails du code thread-safe). Il devrait être enveloppé dans une fonction insert_element()
(ou mieux encore, utiliser une bibliothèque appropriée).
face au problème de l'ABA:
Je ne pense pas que le problème de l'ABA est pertinent à ce code « ajouter un élément à la tête d'une liste ». Disons qu'un thread veut ajouter l'objet X
à la liste et lorsqu'il exécute elem->next = head
, head
a la valeur A1
.
Alors avant que le __sync_val_compare_and_swap()
est exécuté, un autre ensemble de fils arrive et:
- supprime
A1
de la liste, ce qui rend le point head
à B
- fait tout avec un objet
A1
et le libère
- alloue un autre objet,
A2
qui se trouve être à la même adresse que A1
était
- ajoute
A2
à la liste de sorte que les points head
maintenant A2
Depuis A1
et A2
ont le même identifiant/adresse, ceci est une instance du problème de l'ABA.
Cependant, il n'a pas d'importance dans ce cas puisque le thread ajoutant objet X
ne se soucie pas que les head
pointe vers un objet différent de celui qu'il a commencé avec - tout ce qu'il prend soin est que quand X
est mis en attente:
- la liste est cohérente,
- aucun objet sur la liste ont été perdus, et
- pas d'objets autres que
X
ont été ajoutés à la liste (par ce fil)
Ces codes souffrent d'un problème ABA. – ddoman
@ddoman: pourriez-vous nous expliquer comment vous y prendre pour le réparer? Ou utiliseriez-vous simplement l'une des méthodes suggérées dans l'article wikipedia sur le problème ABA? – Aktau
Je ne suis pas sûr que le problème ABA est un problème ici - j'ai mis à jour la réponse pour indiquer pourquoi. Si je me trompe, j'apprécierais les détails. –