Est-il vrai que C ++ 0x viendra sans sémaphores? Il y a déjà quelques questions sur Stack Overflow concernant l'utilisation des sémaphores. Je les utilise (sémaphores posix) tout le temps pour laisser un thread attendre un événement dans un autre thread:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Si je faisais ça avec un mutex:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
Problème: C'est moche et il n'est pas garanti que thread1 verrouille le mutex en premier (étant donné que le même thread doit verrouiller et déverrouiller un mutex, vous ne pouvez pas non plus verrouiller event1 avant que thread0 et thread1 ne démarrent).
Donc, puisque boost n'a pas non plus de sémaphores, quel est le moyen le plus simple d'y parvenir?
Réponses:
Vous pouvez facilement en créer un à partir d'un mutex et d'une variable de condition:
la source
while(!count_)
boucle.Sur la base de la réponse de Maxim Yegorushkin , j'ai essayé de créer l'exemple en style C ++ 11.
la source
cv.wait(lck, [this]() { return count > 0; });
J'ai décidé d'écrire le sémaphore C ++ 11 le plus robuste / générique que je pouvais, dans le style de la norme autant que je le pouvais (notez que
using semaphore = ...
vous utiliseriez normalement le nomsemaphore
similaire à l'utilisation normale destring
notbasic_string
):la source
wait_for
andwait_until
avec le prédicat renvoient une valeur booléenne (pas un `std :: cv_status).std::size_t
n'est pas signé donc le décrémenter en dessous de zéro est UB, et il le sera toujours>= 0
. IMHOcount
devrait être unint
.en accord avec les sémaphores posix, j'ajouterais
Et je préfère de loin utiliser un mécanisme de synchronisation à un niveau d'abstraction pratique, plutôt que de toujours copier-coller une version assemblée en utilisant des opérateurs plus basiques.
la source
Vous pouvez également consulter cpp11-on-multicore - il a une implémentation de sémaphore portable et optimale.
Le référentiel contient également d'autres goodies de threading qui complètent le threading c ++ 11.
la source
Vous pouvez travailler avec des variables mutex et conditionnelles. Vous obtenez un accès exclusif avec le mutex, vérifiez si vous voulez continuer ou si vous devez attendre l'autre extrémité. Si vous avez besoin d'attendre, vous attendez dans une condition. Lorsque l'autre thread détermine que vous pouvez continuer, il signale la condition.
Il y a un court exemple dans la bibliothèque boost :: thread que vous pouvez très probablement simplement copier (les bibliothèques de threads C ++ 0x et boost sont très similaires).
la source
wait()
est traduite en "verrouiller, vérifier le nombre si le décompte est différent de zéro et continuer; si zéro attente à la condition" alors quepost
serait "verrouiller, compteur d'incrément, signal s'il était de 0 "Peut également être utile wrapper de sémaphore RAII dans les threads:
Exemple d'utilisation dans une application multithread:
la source
C ++ 20 aura enfin des sémaphores -
std::counting_semaphore<max_count>
.Ceux-ci auront (au moins) les méthodes suivantes:
acquire()
(blocage)try_acquire()
(non bloquant, retourne immédiatement)try_acquire_for()
(non bloquant, prend une durée)try_acquire_until()
(non bloquant, prend un certain temps pour arrêter d'essayer)release()
Cela n'est pas encore répertorié sur cppreference, mais vous pouvez lire ces diapositives de présentation CppCon 2019 ou regarder la vidéo . Il y a aussi la proposition officielle P0514R4 , mais je ne suis pas sûr que ce soit la version la plus à jour.
la source
J'ai trouvé que shared_ptr et low_ptr, un long avec une liste, ont fait le travail dont j'avais besoin. Mon problème était que plusieurs clients souhaitaient interagir avec les données internes d'un hôte. En règle générale, l'hôte met à jour les données de lui-même, cependant, si un client le demande, l'hôte doit arrêter la mise à jour jusqu'à ce qu'aucun client n'accède aux données de l'hôte. En même temps, un client peut demander un accès exclusif, de sorte qu'aucun autre client, ni l'hôte, ne puisse modifier ces données d'hôte.
Comment j'ai fait cela, j'ai créé une structure:
Chaque client aurait un membre de tel:
Ensuite, l'hôte aurait un membre faible_ptr pour l'exclusivité, et une liste de points faibles pour les verrous non exclusifs:
Il existe une fonction pour activer le verrouillage et une autre fonction pour vérifier si l'hôte est verrouillé:
Je teste les verrous dans LockUpdate, IsUpdateLocked et périodiquement dans la routine de mise à jour de l'hôte. Tester un verrou est aussi simple que de vérifier si le low_ptr a expiré et de supprimer tout expiré de la liste m_locks (je ne le fais que pendant la mise à jour de l'hôte), je peux vérifier si la liste est vide; en même temps, j'obtiens un déverrouillage automatique lorsqu'un client réinitialise le shared_ptr auquel il s'accroche, ce qui se produit également lorsqu'un client est détruit automatiquement.
L'effet global est que, puisque les clients ont rarement besoin d'exclusivité (généralement réservée aux ajouts et aux suppressions uniquement), la plupart du temps une demande à LockUpdate (false), c'est-à-dire non exclusive, réussit tant que (! M_exclusiveLock). Et un LockUpdate (true), une demande d'exclusivité, ne réussit que lorsque (! M_exclusiveLock) et (m_locks.empty ()).
Une file d'attente pourrait être ajoutée pour atténuer les verrous exclusifs et non exclusifs, cependant, je n'ai eu aucune collision jusqu'à présent, donc j'ai l'intention d'attendre que cela se produise pour ajouter la solution (surtout, j'ai donc une condition de test dans le monde réel).
Jusqu'à présent, cela fonctionne bien pour mes besoins; Je peux imaginer la nécessité d'étendre cela, et certains problèmes qui pourraient survenir lors d'une utilisation étendue, cependant, cela a été rapide à implémenter et n'a nécessité que très peu de code personnalisé.
la source
Au cas où quelqu'un serait intéressé par la version atomique, voici l'implémentation. Les performances sont attendues meilleures que la version mutex & condition variable.
la source
wait
code doive effectuer plusieurs boucles. Quand il se débloquera enfin, il prendra la mère de toutes les branches mal prédites car la prédiction de boucle du processeur prédira certainement qu'il bouclera à nouveau. Je pourrais énumérer beaucoup plus de problèmes avec ce code.wait
boucle consommera des ressources de micro-exécution du processeur lorsqu'elle tourne. Supposons qu'il se trouve dans le même noyau physique que le thread qui est censé le fairenotify
- cela ralentira terriblement ce thread.wait
boucle pour le même sémaphore. Ils écrivent tous les deux à pleine vitesse sur la même ligne de cache, ce qui peut ralentir les autres cœurs jusqu'à l'exploration en saturant les bus inter-cœurs.