L'écriture d'un (filature) fil de barrière à l'aide de c++11 atomics
Je suis en train de me familiariser avec le c++11 atomics, j'ai donc essayé d'écrire une barrière de classe pour les threads (avant que quelqu'un se plaint de ne pas utiliser de classes existantes: c'est plus pour l'apprentissage/l'amélioration de soi qu'à aucun besoin réel). ma classe ressemble fondamentalement comme suit:
class barrier
{
private:
std::atomic<int> counter[2];
std::atomic<int> lock[2];
std::atomic<int> cur_idx;
int thread_count;
public:
//constructors...
bool wait();
};
Tous les membres sont initialisées à zéro, à l'exception de thread_count, qui détient la plus appropriée de les compter.
J'ai implémenté la fonction attendre que
int idx = cur_idx.load();
if(lock[idx].load() == 0)
{
lock[idx].store(1);
}
int val = counter[idx].fetch_add(1);
if(val >= thread_count - 1)
{
counter[idx].store(0);
cur_idx.fetch_xor(1);
lock[idx].store(0);
return true;
}
while(lock[idx].load() == 1);
return false;
Cependant, quand on essaie de l'utiliser avec les deux fils (thread_count
est 2) blé premier thread est dans la boucle d'attente, mais le deuxième thread n'a pas de déverrouiller la barrière (il semble qu'il n'a même pas eu à int val = counter[idx].fetch_add(1);
, mais je ne suis pas trop sûr de vous. Cependant, quand je suis en utilisant gcc atomique intrinsèques en utilisant volatile int
au lieu de std::atomic<int>
et de l'écriture wait
comme suit:
int idx = cur_idx;
if(lock[idx] == 0)
{
__sync_val_compare_and_swap(&lock[idx], 0, 1);
}
int val = __sync_fetch_and_add(&counter[idx], 1);
if(val >= thread_count - 1)
{
__sync_synchronize();
counter[idx] = 0;
cur_idx ^= 1;
__sync_synchronize();
lock[idx] = 0;
__sync_synchronize();
return true;
}
while(lock[idx] == 1);
return false;
il fonctionne très bien. De ma compréhension, il ne devrait pas y avoir de différences fondamentales entre les deux versions (plus au point si quelque chose que la seconde devrait être moins susceptibles de travailler). Alors, laquelle des situations suivantes s'applique?
- J'ai eu de la chance avec le deuxième de la mise en œuvre et de mon algorithme est de la merde
- Je n'ai pas bien compris
std::atomic
et il y a un problème avec la première variante (mais pas le second) - Il devrait fonctionner, mais la mise en œuvre expérimentale de c++11 bibliothèques n'est pas aussi mature que j'ai espéré
Pour le dossier je suis en utilisant 32 bits avec mingw gcc 4.6.1
Le code d'appel qui ressemble à ceci:
spin_barrier b(2);
std::thread t([&b]()->void
{
std::this_thread::sleep_for(std::chrono::duration<double>(0.1));
b.wait();
});
b.wait();
t.join();
Depuis mingw n'est pas whave <thread>
les en-têtes de jet-je utiliser un auto version écrite de ce qui, fondamentalement, enveloppe le pthread approprié fonctions (avant que quelqu'un pose la question: oui, il fonctionne sans la barrière, de sorte qu'il ne devrait pas être un problème avec l'emballage)
Toutes les suggestions seraient appréciées.
edit: Explication de l'algorithme pour le rendre plus clair:
thread_count
est le nombre de threads qui doit attendre la barrière (donc sithread_count
fils sont dans la barrière de tous pouvez laisser la barrière).lock
est mis à un lorsque le premier (ou du tout) fil entre la barrière.counter
compte le nombre de threads à l'intérieur de la barrière et est automatiquement incrémenté pour chaque threadif counter>=thread_count
tous les fils sont à l'intérieur de la barrière afin de compteur et de verrouillage sont remis à zéro- sinon, le thread attend le
lock
pour devenir zéro - dans la prochaine utilisation de la barrière de différentes variables (
counter
,lock
) sont utilisés assurer il n'y a pas de problèmes si les threads sont toujours en attente de la première utilisation de la barrière (par exemple, ils avaient été mis en pause lorsque la barrière est levée)
edit2:
J'ai maintenant testé à l'aide de gcc 4.5.1 sous linux, où les deux versions semblent très bien fonctionner, ce qui semble signaler un problème avec mingw est std::atomic
, mais je ne suis pas encore complètement convaincu, depuis la recherche dans le <atomic>
en-tête revaled que la plupart des fonctions simplement appeler le gcc-atomique, ce qui signifie qu'il ne devrait pas bea différence entre les deux versions
__sync_fetch_and_add
). Je dirais, ceux-ci devraient être inutiles avec c++11?J'ai mis en place la version à l'aide de GCC intrinsèques lors de l'une à l'aide de atomics n'ai pas de travail à des fins de comparaison (aka si les deux n'a pas fonctionné, j'aurais cru qu'il y est quelque chose de vraiment mal avec mon algorithme)
N'est-il pas une condition de concurrence dans le
if load then store
? Ne devrait-ce pas être un if (x.exchange(1))
ou quelque chose comme ça?SB: non, pas vraiment car le si n'est même pas nécessaire, il existe,
store(1)
sans if
devrait fonctionner tout aussi bien, depuis lock
est mis à 1 et, ensemble de retour après tous les fils dans la barrière a passé cette. Le if(lock.load())
n'est là que pour éviter unnecerry accès en écriture à la cacheline (maintenant que j'y pense j'ai pu commencer à définir la prochaine écluse à 1 dans le if(val >= thread_count - 1)
partie, pour obtenir le même effetJe pense que vous avez raison, je n'étais pas le lire attentivement assez.
OriginalL'auteur Grizzly | 2011-11-13
Vous devez vous connecter pour publier un commentaire.
Il semble inutilement compliqué. Essayez cette version plus simple (enfin, je ne l'ai pas testé, j'ai juste pensé:))) :
EDIT:
@Grizzy, je ne trouve pas toutes les erreurs dans votre premier (C++11) de la version et j'ai aussi de lancer une centaine de millions de synchronisation avec les deux fils et qu'il accomplit. J'ai l'exécuter sur un processeur dual-socket/quad-core machine sous GNU/Linux, alors je suis plutôt enclin à croire que votre option 3. - la bibliothèque (ou plutôt, son port pour win32) n'est pas assez mature.
oh non, il l'a fait! Il a sali avec mon saint GNU mise en forme! 😛
Hein. J'ai lu beaucoup de code GNU, je suis sûr que je n'ai pas vu ces demi-tirets. Maintenant, il pourrait être une SORTE d'artefact, ou tout simplement mon sens de l'orientation au moment de faire mon chemin à travers GNU code 🙂 Ok, je vais être plus prudente la prochaine fois
Je woukdn pas dire que ma version est que muc plus complecated, mais cette version n'a pas l'air plus sympa, donc merci pour ça. Malheureusement, il semble avoir les mêmes problèmes que ma version d'origine (juste fait un test rapide, besoin de vérifier certains plus pour être sûr)
OriginalL'auteur chill
Je n'ai aucune idée si cela va être de l'aide, mais l'extrait suivant de Herb Sutter la mise en œuvre d'un concurrent à la file d'attente utilise un spinlock basé sur atomics:
En fait, la Norme fournit un type de cette construction qui est nécessaire pour avoir sans verrouillage des opérations,
std::atomic_flag
. Avec cela, la section critique devrait ressembler à ceci:(Vous pouvez utiliser d'acquérir et de libérer de la mémoire de la commande si vous préférez.)
->
et__
par pouce carré est une bénédiction. Et, vous pouvez avoir les espaces en trop! Je vais pense que je vais aller nettoyer que l'OPMerci 🙂 réels de la file d'attente est en fait assez soignée, trop. C'est sur Dr Dobbs, Mesure de la Performance Parallèle: l'Optimisation Simultanée de la File d'attente.
Je l'ai vu! L'un de ces messages d'avertissement (rester à l'écart de roulés à la main sans verrouillage à moins que...). Je préfère utiliser TBB ou LibCds (PS. formaté OP)
Mais ce choix est-il autre que des roulés à la main? J'ai vu LibCds, est-il bon? Doxygen rend ma peau ramper... Il y a certains gars qui l'a fait "Boost lockfree" qui est pas partie de Boost, mais encore une fois aucune idée de comment il est bon. Est TBB général, C++, ou uniquement pour les processeurs Intel compilateur?
LibCds est sacrément bon. Il a des cartes, des piles, files, listes de commande avec un numéro de générique (enfichable) collecte des ordures stratégies. Le filetage de la bibliothèque peut être branché (j'utilise pthreads), et la plus importante raison pour laquelle il n'est pas dans boost, est les problèmes potentiels de licences avec les algorithmes, IIRC. Modifier en Suivant le lien pour Stimuler lockfree, je me souviens d'une conversation entre le développeur de LibCds et Tim Blechmann sur la mise en œuvre des spécificités et de leur inclusion éventuelle dans boost.
OriginalL'auteur Kerrek SB
Voici une solution élégante à partir du livre C++ de la Simultanéité dans l'Action: la Pratique de Multithreading.
OriginalL'auteur UncleHandsome
Ici est une version simple de la mine :
Utilisation : (à partir de std::lock_guard exemple)
votre mise en œuvre est presque correct. Presque, parce que ici:
bool expected = false; while(!lockVal.compare_exchange_strong( expected,true ) );
après le premier appel àcompare_exchange_strong
, la valeur deexpected
devienttrue
, et par la suite sur le tourne, le verrou permettra à tout le fil à travers. Découvrez std::atomic<> référence. Manière correcte serait:bool expected = false; while(!lockVal.compare_exchange_strong( expected,true ) ) expected = false;
Pas de.
expected
n'est pas un membre de la classe. Chaque thread entrer dans leLock()
méthode devra faire face à un nouveauexpected
variable. C'est juste une valeur sur la pile, qui doit être nettoyé jusqu'à la sortie de laLock()
méthode. Si vous n'avez pas à régler pourfalse
.Non, @ali_bahoo, vous avez tort. 🙂 Vous pouvez également tester votre code, il ne fonctionne pas comme prévu, et c'est parce que la valeur de
expected
sur la pile est remplacée parcompare_exchange_strong
. La référence que j'ai lié, dit: "Compare le contenu de la valeur attendue: - si la valeur est true, il remplace le contenu de la valeur avec val (comme le magasin). - si la valeur est false, il remplace attendu avec le contenu de la valeur ." Cela romptexpected
pour la prochaine itération de boucle.Dieu! A quoi je pensais? Pour ce faire, une meilleure approche est d'utiliser
std::atomic::exchange()
au lieu decompare_exchange_strong
. C'est plus simple.OriginalL'auteur ali_bahoo
Je sais que le fil est un peu vieux, mais puisque c'est toujours les premiers résultats sur google lors de la recherche d'un fil de barrière à l'aide de c++11, je tiens à vous présenter une solution qui se débarrasse de la occupé attente à l'aide de la
std::condition_variable
.Fondamentalement, c'est la solution du froid, mais au lieu de la
while
boucle c'est à l'aide destd::conditional_variable.wait()
etstd::conditional_variable.notify_all()
. Dans mes tests, il semble bien fonctionner.spinlocks sont généralement lockless (libre de mutex)
OriginalL'auteur TeamTiffany
Pourquoi ne pas utiliser std::atomic_flag (à partir de C++11)?
http://en.cppreference.com/w/cpp/atomic/atomic_flag
Voici comment je voudrais écrire ma filer de la barrière de classe:
OriginalL'auteur mchiasson
De vol directement à partir de docs
spinlock.h
usage.cpp
OriginalL'auteur Mark K Cowan