Selon le projet final C++ 0x, il n'y a aucun moyen de demander à un thread de se terminer. Cela dit, si nécessaire, nous devons mettre en œuvre une solution de bricolage.
D'un autre côté, boost :: thread fournit un mécanisme pour interrompre un thread de manière sûre.
À votre avis, quelle est la meilleure solution? Concevoir votre propre "mécanisme d'interruption" coopératif ou devenir natif?
Toutes les spécifications de langue indiquent que le support n'est pas intégré dans la langue. boost::thread::interrupt
a également besoin de la fonction thread:
Lorsque le thread interrompu exécute ensuite l'un des points d'interruption spécifiés (ou s'il est actuellement bloqué lors de son exécution)
par exemple, lorsque la fonction de thread ne donne pas à l'appelant une chance d'interrompre, vous êtes toujours bloqué.
Je ne sais pas ce que vous entendez par "devenir natif" - il n'y a pas de support natif, sauf si vous êtes envoûté par boost:threads
.
Pourtant, j'utiliserais un mécanisme explicite. Il faut quand même penser à avoir suffisamment de points d'interruption, pourquoi ne pas les rendre explicites? Le code supplémentaire est généralement marginal dans mon expérience, bien que vous deviez peut-être changer certaines attentes d'un seul objet à plusieurs objets, ce qui, selon votre bibliothèque, peut sembler plus laid.
On pourrait également tirer le "ne pas utiliser d'exceptions pour le flux de contrôle", mais par rapport à jouer avec les threads, ce n'est qu'une directive.
L'utilisation d'un handle natif pour annuler un thread est une mauvaise option en C++ car vous devez détruire tous les objets alloués à la pile. C'était la raison principale pour laquelle ils n'incluaient pas une opération d'annulation.
Boost.Thread fournit un mécanisme d'interruption, qui doit se regrouper sur n'importe quelle primitive en attente. Comme cela peut être coûteux en tant que mécanisme général, la norme ne l'a pas inclus.
Vous devrez l'implémenter vous-même. Voir ma réponse ici à une question similaire sur la façon de l'implémenter par vous-même. Pour terminer la solution, une interruption doit être lancée lorsqu'elle est interrompue et le thread doit intercepter cette interruption et terminer.
Il n'est pas sûr de terminer un thread, car vous n'auriez aucun contrôle sur l'état des structures de données sur lesquelles vous travailliez à ce moment.
Si vous souhaitez interrompre un thread en cours d'exécution, vous devez implémenter votre propre mécanisme. À mon humble avis si vous en avez besoin, votre conception n'est pas préparée pour plusieurs threads.
Si vous voulez simplement attendre la fin d'un thread, utilisez join () ou un futur.
Il n'est pas sûr de terminer un thread de manière préventive car l'état de l'ensemble du processus devient indéterminé après ce point. Le thread peut avoir acquis une section critique avant d'être terminé. Cette section critique ne sera désormais plus publiée. Le tas peut devenir définitivement verrouillé, etc.
Le boost::thread::interrupt
la solution fonctionne en demandant gentiment. Il n'interrompra qu'un thread faisant quelque chose qui est interruptible, comme attendre une variable de condition Boost.Thread, ou si le thread fait l'une de ces choses après l'appel de l'interruption. Même alors, le fil n'est pas mis sans ménagement dans le hachoir à viande comme, par exemple, la fonction TerminateThread
de Win32, il induit simplement une exception, qui, si vous avez été un bon codeur et utilisé RAII partout , se nettoiera après lui-même et quittera le thread avec grâce.
La mise en œuvre d'une solution de bricolage est la plus logique, et cela ne devrait vraiment pas être si difficile à faire. Vous aurez besoin d'une variable partagée que vous lisez/écrivez de manière synchrone, indiquant si le thread est invité à se terminer, et votre thread lit périodiquement à partir de cette variable lorsqu'il est dans un état où il peut être interrompu en toute sécurité. Lorsque vous souhaitez interrompre un thread, vous écrivez simplement de manière synchrone dans cette variable, puis vous joignez le thread. En supposant qu'elle coopère correctement, elle doit remarquer que la variable a été écrite et arrêtée, ce qui a pour conséquence que la fonction de jointure ne bloque plus.
Si vous deveniez natif, vous n'y gagneriez rien; vous jeteriez simplement tous les avantages d'un mécanisme de threading standard et multiplateforme OOP. Pour que votre code soit correct, le thread devrait s'arrêter en coopération, ce qui implique la communication décrit ci-dessus.
Voici mon humble implémentation d'un annuleur de threads (pour C++ 0x). J'espère que ce sera utile.
// Class cancellation_point
#include <mutex>
#include <condition_variable>
struct cancelled_error {};
class cancellation_point
{
public:
cancellation_point(): stop_(false) {}
void cancel() {
std::unique_lock<std::mutex> lock(mutex_);
stop_ = true;
cond_.notify_all();
}
template <typename P>
void wait(const P& period) {
std::unique_lock<std::mutex> lock(mutex_);
if (stop_ || cond_.wait_for(lock, period) == std::cv_status::no_timeout) {
stop_ = false;
throw cancelled_error();
}
}
private:
bool stop_;
std::mutex mutex_;
std::condition_variable cond_;
};
// Usage example
#include <thread>
#include <iostream>
class ThreadExample
{
public:
void start() {
thread_ = std::unique_ptr<std::thread>(
new std::thread(std::bind(&ThreadExample::run, this)));
}
void stop() {
cpoint_.cancel();
thread_->join();
}
private:
void run() {
std::cout << "thread started\n";
try {
while (true) {
cpoint_.wait(std::chrono::seconds(1));
}
} catch (const cancelled_error&) {
std::cout << "thread cancelled\n";
}
}
std::unique_ptr<std::thread> thread_;
cancellation_point cpoint_;
};
int main() {
ThreadExample ex;
ex.start();
ex.stop();
return 0;
}
Mon implémentation de threads utilise l'idiome pimpl, et dans la classe Impl j'ai une version pour chaque OS que je supporte et aussi une qui utilise boost, donc je peux décider laquelle utiliser lors de la construction du projet.
J'ai décidé de faire deux classes: l'une est Thread, qui n'a que les services de base fournis par le système d'exploitation; et l'autre est SafeThread, qui hérite de Thread et a une méthode d'interruption collaborative.
Le thread a une méthode terminate () qui effectue une terminaison intrusive. Il s'agit d'une méthode virtuelle surchargée dans SafeThread, où elle signale un objet événement. Il existe une méthode (statique) yeld () que le thread en cours d'exécution doit appeler de temps en temps; cette méthode vérifie si l'objet événement est signalé et, si oui, lève une exception interceptée au niveau de l'appelant du point d'entrée du thread, mettant ainsi fin au thread. Dans ce cas, il signale un deuxième objet événement afin que l'appelant de terminate () puisse savoir que le thread a été arrêté en toute sécurité.
Pour les cas où il existe un risque de blocage, SafeThread :: terminate () peut accepter un paramètre de délai d'expiration. Si le délai expire, il appelle Thread :: terminate (), tuant ainsi le thread de manière intrusive. Il s'agit d'une dernière ressource lorsque vous avez quelque chose que vous ne pouvez pas contrôler (comme une API tierce) ou dans des situations dans lesquelles un blocage fait plus de dégâts que des fuites de ressources et autres.
J'espère que cela sera utile pour votre décision et vous donnera une image suffisamment claire de mes choix de conception. Sinon, je peux poster des fragments de code pour clarifier si vous le souhaitez.
Je suis d'accord avec cette décision. Par exemple, .NET permet d'interrompre n'importe quel thread de travail, et je n'utilise jamais cette fonctionnalité et je ne recommande de le faire à aucun programmeur professionnel. Je veux décider moi-même quand un thread de travail peut être interrompu et quelle est la façon de procéder. C'est différent pour le matériel, les E/S, l'interface utilisateur et d'autres threads. Si le thread peut être arrêté à n'importe quel endroit, cela peut entraîner un comportement indéfini du programme avec la gestion des ressources, les transactions, etc.