Bibliothèque de threads C ++ 11 (6) Variables de condition

La variable de condition est une primitive de synchronisation de threads qui permet à plusieurs threads de communiquer entre eux.
Il peut faire attendre certains threads (éventuellement timeout), un autre thread peut émettre une notification d'exécution continue, et la variable de condition est toujours liée au mutex. ———— Description par Cppref des variables de condition

1. Pourquoi avons-nous besoin de variables de condition?

Pour faire simple, les variables de condition peuvent accomplir des tâches que les mutex ne peuvent pas (efficacement) faire dans certaines situations. Cela ressemble à un blocage de thread jusqu'à ce qu'un autre thread réponde à une certaine exigence avant de commencer l'exécution. Si un verrou mutex est utilisé pour implémenter un tel processus,

2. Prise en charge C ++ des variables de condition

#include <condition_variable>

  • condition_variable (C ++ 11) fournit une variable de condition de type unique_lock (classe)
  • condition_variable_any (C ++ 11) Fournit une variable de condition (classe) de tout type de verrou
  • notify_all_at_thread_exit (C ++ 11) Planifie un appel à notify_all lorsque le thread se termine complètement (fonction)
  • cv_status (C ++ 11) Énumère tous les résultats possibles (time_out ou no_timeout) (type d'énumération) lors de l'utilisation de l'attente temporisée dans les variables de condition

Troisièmement, la variable de condition condition_variable

3.1 Présentation

La variable de condition est une primitive de synchronisation qui peut être utilisée pour bloquer un ou plusieurs threads en même temps, sachant que d'autres threads modifient la condition partagée et notifient la variable de condition.

Si un thread tente de modifier une variable partagée, il doit:

  • Obtenez std :: mutex (le plus typique est via std :: lock_gurad)
  • Modifier lorsque le verrou est obtenu
  • Appelez notify_one ou notify_all sur la variable de condition (ce verrou n'a pas besoin d'être acquis en premier)

Veuillez noter que même si la variable partagée est de type atomique, elle doit encore être modifiée sous le mutex et publiée correctement dans le thread en attente. ???

Tout thread qui a l'intention d'attendre std :: condition_variable doit:

  • Pour obtenir std :: unique_lockstd :: mutex sur le même mutex utilisé pour protéger les variables partagées,
    effectuez l'une des opérations suivantes:
  • Vérifiez la condition, si elle a été mise à jour ou rappelée
  • Exécutez wait, wait_for ou wait_until, et l'opération d'attente libère automatiquement le mutex et suspend l'exécution du thread.
  • Lorsque la variable condition_variable est notifiée, la limite de temps disparaît ou un faux réveil se produit, le thread est réveillé et l'exclusion mutuelle est automatiquement rétablie. Après cela, le thread doit vérifier la condition et continuer à attendre si le réveil est faux. Si vous ne voulez pas trop de problèmes, vous pouvez utiliser wait, wait_foret les wait_untilsurcharges de fonction prédicat qui peut vous aider à compléter ce qui précède trois choses, la procédure simplifiée de l' écriture.

! ! std :: condition_variable ne peut être utilisé qu'avec std :: unique_lockstd :: mutex; cette restriction permet à ce mécanisme d'atteindre la plus grande efficacité sur certaines plates-formes. Bien sûr, si vous souhaitez utiliser des variables de condition qui n'ont rien à voir avec le type de verrou, vous pouvez essayer son frère :: std::condition_variable_any.

3.2 Fonctions des membres

Le constructeur n'a que aucun constructeur par défaut de paramètre, aucune copie, aucune affectation. Les autres sont deux types de fonctions membres, l'une est la méthode de notification et l'autre est la méthode d'attente.

Méthode de notification: avertissez tout le monde notify_oneou avertissez une personne notify_all.

Méthode d'attente: attendez wait, attendez un certain temps wait_for et attendez un certain moment wait_until. La méthode utilisée est "la variable de condition appelle une certaine méthode pour attendre unique_lcok"

cond.wait(ulo);

L'appel de la méthode wait provoque le blocage du thread actuel jusqu'à ce que la variable de condition soit notifiée, ou qu'un faux réveil se produise, éventuellement en boucle jusqu'à ce qu'un certain prédicat soit satisfait. Pour éviter les faux réveils, vous pouvez utiliser des prédicats:

template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );

Cela équivaut à la déclaration suivante:

while (!pred()) {
    
    //不满足继续等待
    wait(lock);
}

Ce prédicat doit être compris comme une condition de blocage de contact.

Troisièmement, l'instance de variable de condition

Cet exemple implémente l'impression séquentielle de threads simultanés:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

class ConditionVarTest
{
    
    
public:
    void printone();
    void printtwo();
    void printthree();
    mutex lo;
    condition_variable cond;
    int i=0;
};

void ConditionVarTest::printone(){
    
    
    cout<<"1";
    i=1;
    cond.notify_all();
}
 
void ConditionVarTest::printtwo(){
    
    
    unique_lock<mutex> ulo(lo);
    cond.wait(ulo,[this]{
    
    return i==1;});
    cout<<"2";
    i=2;
    cond.notify_one();
}

void ConditionVarTest::printthree(){
    
    
    unique_lock<mutex> ulo(lo);
    cond.wait(ulo,[this]{
    
    return i==2;});
    cout<<"3";

}

int main()
{
    
    
    ConditionVarTest a;
    thread th1(&ConditionVarTest::printone,&a);
    thread th2(&ConditionVarTest::printtwo,&a);
    thread th3(&ConditionVarTest::printthree,&a);
    
    
    th1.join();
    th2.join();
    th3.join();
    
    return 0;
    
}

Quatre, faux réveil

La variable de condition doit également prêter attention au faux réveil , qui apparaît souvent dans le modèle de conception production-consommation:

  • Lorsque le nombre de consommateurs (CONSUMER_NUM) est de 1, aucun autre thread n'est en concurrence pour la file d'attente et les faux réveils ne seront pas déclenchés.
  • Lorsque le nombre de producteurs (PRODUCTER_NUM) est 1,
    lorsque notify_one est utilisé pour notifier le thread consommateur, le faux réveil ne se produira pas, car chaque fois qu'un seul thread consommateur recevra un signal et sera réveillé, cela ne se produira pas avant le le produit est consommé Il y a un nouveau signal.
    Lorsque notify_all est utilisé pour notifier un thread consommateur, un faux réveil se produit et plusieurs threads consommateur sont réveillés après avoir reçu un signal. Avant qu'un thread ne soit réveillé, d'autres threads peuvent être réveillés en premier pour maintenir le verrou et consommer le produit.
  • Lorsque le nombre de producteurs (PRODUCTER_NUM) est supérieur à 1, notify_one ou notify_all sera faussement réveillé. Lorsque plusieurs producteurs utilisent notify_one, plusieurs threads seront réveillés. Il est possible que l'un d'entre eux soit traité très rapidement. Toutes les données est traité, puis le thread qui est ensuite réveillé n'a pas de données à traiter

Pour faire face aux faux réveils, les consommateurs utilisent while pour vérifier à plusieurs reprises le nombre de produits après leur réveil. Pourquoi utiliser while Dans un seul cœur, l'utilisation de broadcast () provoquera un effet de groupe passionnant et conduira à un faux réveil, mais l'utilisation de notify () ne provoquera évidemment pas de faux réveil (seul un certain thread sera réveillé). Mais sous les processeurs multi-core, pthread_cond_signal peut activer plus d'un thread bloqué sur la variable de condition (dans le cas du multi-core: plusieurs threads sont réveillés en même temps lors de la notification). Par conséquent, lorsqu'un thread appelle pthread_cond_signal (), plusieurs threads qui appellent pthread_cond_wait () ou pthread_cond_timedwait () reviennent. Cet effet est appelé «effet de groupe de choc». Si jugé par if, plusieurs threads en attente seront réveillés lorsque la condition if est remplie (false), mais en fait la condition n'est pas remplie, les biens de consommation produits par le producteur ont été consommés par le premier thread, entraînant un "faux réveil ". Par conséquent, il est nécessaire d'utiliser while pour réévaluer la condition afin d'éviter un faux réveil.

[1] https://www.jianshu.com/p/01ad36b91d39
[2] https://blog.csdn.net/shizheng163/article/details/83661861

Je suppose que tu aimes

Origine blog.csdn.net/weixin_39258979/article/details/114199315
conseillé
Classement