2009-04-10 6 views
6

Je suis dans une situation où j'ai besoin de la somme atomique de deux valeurs en mémoire. Le code I hérité va comme ceci:Y at-il un moyen que je puisse faire deux lectures atomiques?

int a = *MemoryLocationOne; 
memory_fence(); 
int b = *MemoryLocationTwo; 
return (a + b) == 0; 

L'individu lit a et b sont atomiques, et toutes les écritures ailleurs dans le code à ces deux emplacements de mémoire sont également Lockless atomique. Cependant, le problème est que les valeurs des deux emplacements peuvent changer et changent entre les deux lectures.

Alors, comment rendre cette opération atomique? Je sais tout à propos de CAS, mais il a tendance à impliquer seulement des opérations de lecture-modification-écriture atomique et ce n'est pas tout à fait ce que je veux faire ici.

Y at-il un moyen de le faire, ou est la meilleure option pour refactoriser le code de sorte que j'ai seulement besoin de vérifier une valeur? Edit: Merci, je n'ai pas mentionné que je voulais faire ceci sans clé dans la première révision, mais certaines personnes l'ont repris après ma seconde révision. Je sais que personne ne croit les gens quand ils disent des choses comme ça, mais je ne peux pas utiliser les verrous pratiquement. Je devrais imiter un mutex avec atomics et ce serait plus de travail que de refactoriser le code pour garder une trace au lieu de deux. Pour l'instant, ma méthode d'investigation consiste à tirer parti du fait que les valeurs sont consécutives et à les saisir atomiquement avec une lecture de 64 bits, dont je suis sûr qu'elles sont atomiques sur mes plates-formes cibles. Si quelqu'un a de nouvelles idées, s'il vous plaît contribuer! Merci.

Répondre

3

Si vous avez vraiment besoin d'assurer que a et b ne changent pas pendant que vous faites ce test, vous devez utiliser la même synchronisation pour tous accès à a et b. C'est ton seul choix. Chaque lecture et chaque écriture dans l'une ou l'autre de ces valeurs doivent utiliser le même guide de mémoire, le même synchroniseur, le même sémaphore, le même verrou ou tout autre mécanisme utilisé.

Avec cela, vous pouvez vous assurer que si vous:

memory_fence_start(); 
int a = *MemoryLocationOne; 
int b = *MemoryLocationTwo; 
int test = (a + b) == 0; 
memory_fence_stop(); 

return test; 

alors a ne changera pas pendant que vous lisez b. Mais encore une fois, vous devez utiliser le même mécanisme de synchronisation pour tous accéder à a et à b. Pour refléter une modification ultérieure à votre question que vous cherchez une méthode sans verrou, eh bien, cela dépend entièrement du processeur que vous utilisez et combien de temps sont a et b et si oui ou non ces emplacements de mémoire sont consécutifs et alignés correctement. En supposant que ceux-ci sont consécutifs en mémoire et 32 ​​bits chacun et que votre processeur a une lecture atomique 64 bits, alors vous pouvez émettre une lecture atomique 64 bits pour lire les deux valeurs, analyser les deux valeurs de la Valeur 64 bits, faites le calcul et renvoyez ce que vous voulez retourner.En supposant que vous n'avez jamais besoin d'une mise à jour atomique à "a et b en même temps" mais seulement des mises à jour atomiques à "a" ou à "b" en isolation, alors cela fera ce que vous voulez sans verrous.

+0

Ils se trouvent des adresses consécutives de 32 bits et les processeurs dont j'ai besoin de coder pour travailler ont atomique 64 bits lectures si elles sont correctement alignées, donc Cela ressemble à la voie à suivre. –

+0

Ah, dans ce cas, vous pouvez écrire un assemblage ou simplement vérifier la sortie de l'assemblage de votre compilateur pour vérifier que l'utilisation d'un type 64 bits fera l'affaire. Dans ce cas, vous pouvez aller sans verrou. – Eddie

3

Vous auriez à vous assurer que partout où l'une des deux valeurs était lue ou écrite, elles étaient entourées d'une barrière de mémoire (verrou ou section critique).

// all reads... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    a = *MemoryLocationOne; 
    b = *MemoryLocationTwo; 
} 

...

// all writes... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    *MemoryLocationOne = someValue; 
    *MemoryLocationTwo = someOtherValue; 
} 
1

Il n'y a vraiment aucun moyen de le faire sans un verrou. Aucun processeur n'a une double lecture atomique, autant que je sache.

+0

Même si un processeur avait une double lecture atomique, cela aiderait seulement si les deux adresses mémoire en question étaient correctement alignées et consécutives. – Eddie

+0

Non, un processeur pourrait permettre une lecture atomique de deux emplacements de mémoire arbitraires. – Zifre

+0

Je ne connais aucun processeur qui permette une lecture atomique garantie d'emplacements de mémoire arbitraires non consécutifs, même dans un environnement multiprocesseur, mais je vous croirai sur parole. – Eddie

3

Si vous ciblez x86, vous pouvez utiliser le support de comparaison/échange 64 bits et regrouper les deux int dans un seul mot de 64 bits.

Sous Windows, vous feriez ceci:

// Skipping ensuring padding. 
union Data 
{ 
    struct members 
    { 
     int a; 
     int b; 
    }; 

    LONGLONG _64bitData; 
}; 

Data* data; 


Data captured; 

do 
{ 
    captured = *data; 
    int result = captured.members.a + captured.members.b; 
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData, 
        captured._64BitData, 
        captured._64bitData) != captured._64BitData); 

vraiment laid. Je suggère d'utiliser une serrure - beaucoup plus maintenable.

EDIT: Pour mettre à jour et lire les différentes parties:

data->members.a = 0; 
fence(); 

data->members.b = 0; 
fence(); 

int captured = data->members.a; 

int captured = data->members.b; 
+0

Cela est vrai, mais vous êtes foutu lorsque vous devez mettre à jour un seul de ces éléments. –

+1

Non, vous effectuez des lectures/écritures atomiques sous-jacentes sur les parties 32 bits. – Michael

+1

Et dans le cas où ce n'était pas clair, ma réponse était de montrer comment cela pouvait être fait - je recommande toujours d'éviter les algorithmes sans verrouillage. – Michael

Questions connexes