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é
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
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
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;
}