C++ multithreading learning 05 verrouillage du délai d'attente, verrouillage récursif et verrouillage partagé

1. Verrouillage temporisé Timed_mutex

Fonction : évite les blocages à long terme, peut enregistrer l'acquisition de verrous, plusieurs délais d'attente, peut enregistrer des journaux et obtenir des conditions d'erreur.

Dans 04, parce que try_lock() ne bloquera pas le thread mais occupera toujours les ressources CPU, ajoutez sleep_for (100ms) pour retarder et bloquer le thread pendant un certain temps pour donner une chance aux autres threads, mais ce délai s'appelle this_thread La fonction suivante :

if (!mux.try_lock())
        {
    
    
            cout << "." << flush;
            this_thread::sleep_for(100ms);
            continue;
        }

Vous pouvez également utiliser le délai comme paramètre du constructeur de verrou, en utilisant timed_mutex :

timed_mutex tmux;

void ThreadMainTime(int i)
{
    
    


    for (;;)
    {
    
    
        if (!tmux.try_lock_for(chrono::milliseconds(500)))
        {
    
     
            //如果未在规定时间内拿到锁,那么这段代码可能会出现问题,这里可以进行日志的写入,便于调试
            cout << i << "[try_lock_for timeout]" << endl;
            continue;
        }
        cout << i << "[in]" << endl;
        this_thread::sleep_for(2000ms);
        tmux.unlock();
        this_thread::sleep_for(1ms);
    }
}

De même, afin de s'assurer que le déverrouillage peut libérer des ressources, retardez enfin :

Trois threads sont créés et chaque thread est bloqué pendant 500 ms après avoir tenté de se déverrouiller.
Question : Pourquoi les trois threads se détachent-ils les uns des autres et l'impression n'est-elle pas salissante ?

int main(int argc, char* argv[])
{
    
    
 
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadMainTime, i + 1);
        th.detach();
    }
    getchar();
  }

[in] indique que le thread est entré
[try_lock_for timeout] indique que l'entrée a échoué
insérez la description de l'image ici

2. Verrou récursif (verrou réentrant) recursive_mutex

Le même verrou peut être verrouillé plusieurs fois dans le même thread. Évitez certains scénarios d'utilisation de blocage inutiles
 : une fonction est appelée de manière récursive, et la fonction accède aux données partagées à verrouiller, s'appelle elle-même de manière récursive avant le déverrouillage, et le verrou récursif sera utilisé. Dans un autre scénario, dans une classe, deux fonctions membres f1 et f2 accéderont aux données partagées et seront verrouillées. Appeler f2 après avoir verrouillé f1 nécessite également un verrou récursif. L'implication est que tant que l'opération de verrouillage du même verrou est appelée avant le déverrouillage, afin d'éviter un interblocage, un verrou récursif est requis.

Référence : https://www.zhihu.com/question/448190927/answer/1768286353

recursive_mutex rmux;
void Task1()
{
    
    
    rmux.lock();
    cout << "task1 [in]" << endl;
    rmux.unlock();
}
void Task2()
{
    
    
    rmux.lock();
    cout << "task2 [in]" << endl;
    rmux.unlock();
}
void ThreadMainRec(int i)
{
    
    
    for (;;)
    {
    
    
        rmux.lock(); 
        Task1();
        cout << i << "[in]" << endl;
        this_thread::sleep_for(2000ms);
        Task2();
        rmux.unlock();
        this_thread::sleep_for(1ms);
    }
}

Même s'il s'agit d'un verrou récursif, il doit être déverrouillé plusieurs fois après avoir été verrouillé.

int main(int argc, char* argv[])
{
    
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadMainRec, i + 1);
        th.detach();
    }
    getchar();
 }  

On peut voir que ThreadMainRec n ° 123 est commuté alternativement, et chaque ThreadMainRec exécute la tâche1 et la tâche2 une fois
insérez la description de l'image ici

3. Verrou partagé shared_mutex

Nous rencontrons souvent un tel scénario :
plusieurs (100) threads lisent une ressource en même temps, il n'y aura pas de problème. Lorsqu'un thread modifie cette ressource, ces 100 ressources doivent attendre. À ce moment, il y a d'autres ressources qui veulent modifier cette ressource. La modification de la ressource doit également attendre

Pour résumer :
le thread qui lit les ressources peut être lu par d'autres threads lors de la lecture, mais ne peut pas écrire.
Le thread qui écrit des ressources ne peut ni lire ni écrire lorsque d'autres threads écrivent.
Par conséquent, les threads ayant des besoins différents utilisent des verrous différents et les threads qui lisent utilisent des verrous qui sont lus. Le thread d'écriture utilise le verrou d'écriture. Si
un thread ne fait que lire, il utilise le verrou de lecture. Si
un thread va écrire, il utilise d'abord le verrou de lecture, puis le verrou d'écriture, et puis modifie la ressource.

Maintenant, sacrifiez ces deux verrous :
mutex temporisé partagé c++14 shared_timed_mutex mutex
partagé c++17 mutex_partagé

Prenons l'exemple de la norme C++14 :
shared_timed_mutex stmux ;
verrou pour l'écriture : stmux.lock();
verrou pour la lecture : stmux.lock_shared();

shared_timed_mutex stmux;

void ThreadRead(int i)
{
    
    
    for (;;)
    {
    
    
        stmux.lock_shared();
        cout << i << " Read" << endl;
        //this_thread::sleep_for(500ms);
        stmux.unlock_shared();
        this_thread::sleep_for(1ms);
    }
}
void ThreadWrite(int i)
{
    
    
    for (;;)
    {
    
    
        stmux.lock(); //互斥锁 写入
        cout << i << " Write" << endl;
        this_thread::sleep_for(300ms);
        stmux.unlock();
        this_thread::sleep_for(1ms);
    }
}
int main(int argc, char* argv[])
{
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadWrite, i + 1);
        th.detach();
    }
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadRead, i + 1);
        th.detach();
    }
    getchar();
    return 0;
}

Si vous voulez obtenir le verrou en écriture, vous devez d'abord vous assurer que le verrou en lecture est relâché et que le verrou en écriture est relâché. Si
vous voulez obtenir le verrou en lecture, vous devez seulement vous assurer que le verrou en écriture est relâché.
Vous pouvez voir que le thread de lecture apparaît souvent dans la même ligne et que les trois threads de lecture passent Après la création de la boucle for, si la ressource n'est pas verrouillée par le verrou d'écriture, les trois threads peuvent prendre le verrou de lecture et entrer dans la tâche de thread de cout ensemble. Lorsque le cout << i << " Read" << endl; d'un thread n'est pas terminé (plus d'une ligne après l'assemblage), le cout << i << " Read" << endl; d'un autre thread est envoyé au CPU pour s'exécuter, donc le phénomène de la même ligne apparaîtra, mais pas le thread d'écriture.Il veut s'assurer qu'il n'y a pas de threads de lecture ou de threads actuellement.shared_lock
insérez la description de l'image ici
C++14 implémente un wrapper de propriété de mutex partagé amovible :

int main(int argc, char* argv[])
{
    
    

    {
    
    
        //共享锁
        static shared_timed_mutex  tmux;
        //读取锁 共享锁
        {
    
    
            //调用共享锁 
            shared_lock<shared_timed_mutex> lock(tmux);
            cout << "read data" << endl;
            //退出栈区 释放共享锁
        }
        //写入锁 互斥锁
        {
    
    
            unique_lock<shared_timed_mutex> lock(tmux);
            cout << "write data" << endl;
        }
    }
    return 0;
}

shared_lock<shared_timed_mutex> lock(tmux) ; verrou partagé sur le mutex entrant dans le constructeur

explicit shared_lock(mutex_type& _Mtx)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {
    
     // construct with mutex and lock shared
        _Mtx.lock_shared();
    }

unique_lock<shared_timed_mutex> lock(tmux); Mutex sur le mutex entrant dans le constructeur

explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {
    
     // construct and lock
        _Pmtx->lock();
        _Owns = true;
    }

insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_42567607/article/details/125530759
conseillé
Classement