J'ai deux petites questions sur mon code (ci-dessous) :
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex>
int main()
{
std::mutex m;
int time(0);
std::thread t([&m, &time]()
{
while (1)
{
m.lock();
std::cout << ++time << '\n';
m.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
});
std::thread t2([&m]()
{
while (1)
{
m.lock();
std::cout << "Hello\n";
m.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
});
t.join();
t2.join();
}
Premièrement, c'est peut-être une question bête, mais est-ce que c'est la façon "normale" de se servir des threads ? Enfin je sais qu'ici ce n'est pas très utile, mais j'avais un doute sur la syntaxe (bien que le code marche ).
Et puis je voudrais également savoir pourquoi les deux threads démarrent. Si j'ai bien compris, lorsque l'on active un thread secondaire, le thread principal est bloqué jusqu'à la fin de l'execution de ce dernier. Or là, le t2 se lance alors que t1 est une boucle infinie.
Voilà voilà pour mes questions, merci d'avance
Edit : C'est galère l'éditeur ou alors je suis pas doué
- Edité par 23antoine03 27 mai 2020 à 22:36:39
Vous ne pouvez pas comprendre la récursivité sans d’abord avoir compris la récursivité
Salut ! Syntaxiquement, ton programme est bon, bien que spécial dans l'écriture. Mais il existe beaucoup de façon d'écrire un programme avec un comportement bien défini, et la tienne est correcte.
Basiquement, un thread secondaire est un échantillon de code (ou une sorte de "fonction" comme le dit @Fvirtmann) qui est hébergé dans l'espace mémoire dédié à ton application MAIS qui possède son propre espace mémoire. Cet échantillon de code peut être exécuté parallèlement au thread principal, "en même temps". L'intérêt d'un thread secondaire c'est de permettre à notre thread principal de ne pas bloquer, ce qui réduirait l'efficacité de l'application dans de nombreux cas.
Donc, oui t2 est lancé en même temps que t1 mais c'est normal, car ils sont exécutés parallèlement. Et comme tu verrouille et déverrouille constamment ton mutex sur chacun de tes threads, alors t1/t2 doit juste attendre que t2/t1 ait affiché ce qu'il voulait pour reprendre.
Voilà ! Retiens juste qu'un thread est exécuté parallèlement par rapport au reste des threads, et c'est tout l'intérêt du concept.
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
void timer(int& t)
{
while (1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << ++t << "s\n";
}
}
void sayHello()
{
while (1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(300));
std::cout << "Hello\n";
}
}
int main()
{
int t(0);
std::thread t1(timer, std::ref(t));
std::thread t2(sayHello);
t1.join();
std::cout << "Joining t2\n"; // Jamais affiché
t2.join(); // Fonctionne également en supprimant cette ligne
return 0;
}
J'ai l'impression que les threads sont lancés à leur création (c'était aussi le cas dans l'exemple d'avant). Si je supprime les deux join(), j'ai un erreur mais les threads sont tout de même "activés". Je n'ai plus cette erreur si je supprime seulement l'appel pour t2.
Et ça n'a pas l'air de vous avoir dérangé, donc je pense que c'est faisable, mais j'hésitais entre ma façon de faire et ça :
Tes 2 lambdas gèrent bien 2 threads différents. Il ne faut cependant jamais oublier le principe RAII surtout pour les threads, la forme à utiliser serait plutôt :
std::thread t( [&m, &time]() {
while (1)
{
{
std::lock_guard<std::mutex> lck(m); // prise du mutex en RAII
std::cout << ++time << '\n';
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
});
Dans ton code, si la ligne avec le std::cout venait à produire une exception, le mutex ne serait jamais rendu.
Pour ton second code, à la sortie de main(), tes objets std::thread sont détruits. S'il n'y a pas de join(), à cet instant les threads sont toujours actifs, c'est une erreur grave qui provoque un arrêt immédiat du logiciel. Le 1er join() va attendre la fin du 1er thread qui ne se produit jamais, d'où tout ce qui suit ne sera de toute manière jamais appelé! Il faut toujours prévoir une condition de sortie "propre" pour ses threads, et l'application en pourra se terminer "proprement" que si les threads sont stoppés "proprement"
Tu peux à la place des join() faire un detach(). Le résultat dépend complètement de ton système exploitation. Dans ce cas on va sortir du main() alors que les threads sont toujours actifs!
=> le système peut attendre bêtement et indéfiniement la fin des threads => ou bien le système considère que la fin du thread principal provoque un kill de tous les threads.
J'en avais déjà entendu parler, c'est vrai que je n'y ai pas pensé.
Merci !
Mais ce que je ne comprends pas, c'est que si jamais je veux démarrer le thread t1 (un chrono dans ce cas), je vais bien devoir continuer le code après. Cependant, il ne sera pas exécuté... Et je ne parle pas forcément d'un chrono, je pourrais faire un thread pour la gestion des entrées, ou autre.
PS : detach() ne fonctionne pas si je le mets à la place des join(), mais je vais me renseigner.
=> Effectivement, la méthode ci-dessous fonctionne (j'édite pour me garder un message au cas où j'aie un autre problème)
- Edité par 23antoine03 28 mai 2020 à 11:32:07
Vous ne pouvez pas comprendre la récursivité sans d’abord avoir compris la récursivité
=> le système peut attendre bêtement et indéfiniement la fin des threads
=> ou bien le système considère que la fin du thread principal provoque un kill de tous les threads.
- Edité par Dalfab il y a 27 minutes
D'après ce message ci, le kernel s'occupe de bien nettoyer l'espace mémoire et d'arrêter tous les threads liés au processus lorsque celui ci est notifié de sa fin. Par conséquent, il ne pourrait pas y avoir d'attente indéfinie. Mais si c'est réellement le cas, s'il existe des situations qui font qu'un thread continue bêtement sa route, comment se fait-il que l'OS ne gère pas ce genre de situation ?
@23antoine03 D'abord, oui le thread est exécuté avec son échantillon de code lors de la construction d'un objet std::thread.
Si tu appelles la fonction std::thread::detach() alors, je cite: "Separates the thread of execution from the thread object, allowing execution to continue independently. Any allocated resources will be freed once the thread exits.". Donc tu n'as pas à te faire de peine pour le code qui suit, cette fonction membre ne fait que détacher un thread, et celui-ci sera proprement détruit lors de la fin du processus. Tu peux l'utiliser ainsi:
Là la fonction membre std::thread::detach() détache le thread directement juste après sa construction. Et tu n'as plus besoin d'appeler std::thread::join() car il est détaché du thread principal, il est presque indépendant et peut aller se balader seul dans la prairie jusqu'à ce que Kernel ne vienne le buter... Triste vie qu'ont les threads.
En effet l'éditeur n'est pas très simple à dompter...
=> le système peut attendre bêtement et indéfiniement la fin des threads
=> ou bien le système considère que la fin du thread principal provoque un kill de tous les threads.
- Edité par Dalfab il y a 27 minutes
D'après ce message ci, le kernel s'occupe de bien nettoyer l'espace mémoire et d'arrêter tous les threads liés au processus lorsque celui ci est notifié de sa fin. Par conséquent, il ne pourrait pas y avoir d'attente indéfinie. Mais si c'est réellement le cas, s'il existe des situations qui font qu'un thread continue bêtement sa route, comment se fait-il que l'OS ne gère pas ce genre de situation ?
Ce post est dans le cas que j'ai cité en second. Ça doit être le cas sous Linux. Pour Windows, je crois que l'appli continue. Pour qu'une application s'arrête il faut et il suffit que tout ses threads s'arrêtent. Et que se passe-t-il si un des threads existants est bloqué sur une action "non cancelable"?. que fait Linux dans cas? L'application s'arrête ou bien le kill est forcé quand même quitte à faire tomber le noyau!
C'est très compliqué et très dangereux de tuer un thread qui est en train de faire un appel système. C'est pourquoi le premier cas n'est pas si bête, l'application a encore des choses à faire, donc elle n'est pas terminée.
@Daflab d'accord ! Je ne voyais pas les choses ainsi, c'est vrai que ça parait être logique. Mais donc, imaginons qu'un thread ne s'arrête pas, il fait tourner une boucle infinie, on devra attendre l'arrêt de l'ordinateur pour que son exécution cesse ? Ou il existe un mécanisme dédié à ça ?
A ma connaissance, les syscall Linux sont atomiques et ne peuvent donc être interrompus. Même en envoyant SIGKILL, il ne peut pas mourir au cours d'un syscall mais je peux me tromper ?
Si le processus ne peut être interrompu (sommeil), il ne peut pas être arrêté sous Linux. Comme on dit dans ces cas-là : "dans le doute, reboot !"
Il existe des moyen de forcer un arrêt, ça marche très bien le plus souvent. Un simple Control-C va envoyer un SIGINT à l'application
- Si le thread passe par un point dit "cancelable" (en gros les entrée/sortie le sont presque toujours) ; dans ton cas l'utilisation de la fonction std::cout est un point d'arrêt, le thread sera arrêté. - Si le thread ne fait pas d'appel système (par exemple une simple boucle infinie), il ne peut pas réagir aux événements, pourtant il n'y aurait rien de dangereux pour être arrêté brutalement, là je ne sais pas bien ce qu'il peut se passer concrètement, mais il me semble qu'un simple code avec une boucle for infinie est normalement arrêtable quel que soit le système. - Il n'y vraiment que si le thread est un appel système à la fois bloquant et non interruptible, là on ne peut rien faire. Et je suis d'accord même un SIGKILL n'y fera rien.
D'accord, c'est très intéressant tout ça ! Qu'est ce que tu veux dire par un point cancelable @Dalfab ? Un point qui serait susceptible de stopper l'exécution du programme ?
Je comprends pas en quoi un syscall empêcherait l'arrêt d'un thread ou d'un processus ? Un signal n'est qu'une notification asynchrone, donc même s'il lui revenait le droit de se suicider, ce dont je doute, il le ferait à son retour dans l'espace utilisateur.
Vous ne pouvez pas comprendre la récursivité sans d’abord avoir compris la récursivité
Recueil de code C et C++ http://fvirtman.free.fr/recueil/index.html
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
Vous ne pouvez pas comprendre la récursivité sans d’abord avoir compris la récursivité
En recherche d'emploi.
Vous ne pouvez pas comprendre la récursivité sans d’abord avoir compris la récursivité
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
En recherche d'emploi.
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
En recherche d'emploi.
https://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/