Comment vérifier si un std :: thread est toujours en cours d'exécution?

84

Comment puis-je vérifier si un std::threadest toujours en cours d'exécution (de manière indépendante de la plate-forme)? Il manque une timed_join()méthode et joinable()n'est pas fait pour cela.

J'ai pensé à verrouiller un mutex avec un std::lock_guarddans le thread et à utiliser la try_lock()méthode du mutex pour déterminer s'il est toujours verrouillé (le thread est en cours d'exécution), mais cela me semble inutilement complexe.

Connaissez-vous une méthode plus élégante?

Mise à jour: pour être clair: je veux vérifier si le thread s'est correctement terminé ou non. Un fil «suspendu» est considéré comme en cours d'exécution à cet effet.

kispaljr
la source
Je suppose que vérifier si un thread est toujours en cours n'a d'importance que lorsque vous vous y attendez wait()et, si c'est le cas, si vous ne l'avez pas wait()encore fait, il doit fonctionner par définition. Mais ce raisonnement pourrait être inexact.
ereOn
En fait, j'ai un thread qui se termine dans des conditions exceptionnelles, et je veux vérifier à partir du thread principal s'il est toujours en cours d'exécution, mais je ne veux pas l'attendre (le rejoindre)
kispaljr
1
Qu'entendez-vous exactement par courir? Voulez-vous dire qu'il est en cours de traitement actif plutôt que dans un état d'attente, ou voulez-vous dire que le thread existe toujours et n'est pas terminé?
CashCow
Vous pouvez toujours utiliser boost :)
CashCow
4
Vous n'auriez pas dû accepter une réponse si vous n'en étiez pas satisfait.
Nicol Bolas

Réponses:

117

Si vous êtes prêt à utiliser C ++ 11 std::asyncet std::futureà exécuter vos tâches, vous pouvez utiliser la wait_forfonction de std::futurepour vérifier si le thread fonctionne toujours d'une manière soignée comme celle-ci:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    /* Run some task on new thread. The launch policy std::launch::async
       makes sure that the task is run asynchronously on a new thread. */
    auto future = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(3s);
        return 8;
    });

    // Use wait_for() with zero milliseconds to check thread status.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    auto result = future.get(); // Get result.
}

Si vous devez utiliser, std::threadvous pouvez utiliser std::promisepour obtenir un futur objet:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    // Create a promise and get its future.
    std::promise<bool> p;
    auto future = p.get_future();

    // Run some task on a new thread.
    std::thread t([&p] {
        std::this_thread::sleep_for(3s);
        p.set_value(true); // Is done atomically.
    });

    // Get thread status using wait_for as before.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    t.join(); // Join thread.
}

Ces deux exemples donneront:

Thread still running

C'est bien sûr parce que l'état du thread est vérifié avant la fin de la tâche.

Mais là encore, il pourrait être plus simple de le faire comme d'autres l'ont déjà mentionné:

#include <thread>
#include <atomic>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    std::atomic<bool> done(false); // Use an atomic flag.

    /* Run some task on a new thread.
       Make sure to set the done flag to true when finished. */
    std::thread t([&done] {
        std::this_thread::sleep_for(3s);
        done = true;
    });

    // Print status.
    if (done) {
        std::cout << "Thread finished" << std::endl;
    } else {
        std::cout << "Thread still running" << std::endl;
    }

    t.join(); // Join thread.
}

Éditer:

Il y a aussi la std::packaged_taskpossibilité d'utiliser std::threadpour une solution plus propre que d'utiliser std::promise:

#include <future>
#include <thread>
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono_literals;

    // Create a packaged_task using some task and get its future.
    std::packaged_task<void()> task([] {
        std::this_thread::sleep_for(3s);
    });
    auto future = task.get_future();

    // Run task on new thread.
    std::thread t(std::move(task));

    // Get thread status using wait_for as before.
    auto status = future.wait_for(0ms);

    // Print status.
    if (status == std::future_status::ready) {
        // ...
    }

    t.join(); // Join thread.
}
Snps
la source
2
Bonne réponse. J'ajouterais que cela fonctionne également avec des threads sans aucune valeur de retour et futur <void>
kispaljr
Quelle est la raison de ce code std::atomic<bool> done(false);? N'est-ce pas l' boolatome par défaut?
Hi-Angel
6
@YagamyLight En C ++, rien n'est atomique par défaut sauf s'il est enveloppé dans un fichier std::atomic. sizeof(bool)est définie par l'implémentation et peut être> 1, il est donc possible qu'une écriture partielle se produise. Il y a aussi la question de la cohérence du cache.
Snps
1
notez que std :: chrono_literals nécessitera C ++ 14 pour être compilé
Patrizio Bertoni
6

Une solution simple consiste à avoir une variable booléenne que le thread définit sur true à intervalles réguliers, et qui est vérifiée et définie sur false par le thread qui souhaite connaître l'état. Si la variable est fausse pendant trop longtemps, le thread n'est plus considéré comme actif.

Un moyen plus sûr pour les threads consiste à avoir un compteur qui est augmenté par le thread enfant, et le thread principal compare le compteur à une valeur stockée et si la même chose après trop longtemps, le thread enfant est considéré comme inactif.

Notez cependant qu'il n'y a aucun moyen dans C ++ 11 de tuer ou de supprimer réellement un thread qui s'est bloqué.

Edit Comment vérifier si un thread s'est correctement arrêté ou non: Fondamentalement, la même technique que celle décrite dans le premier paragraphe; Avoir une variable booléenne initialisée à false. La dernière chose que fait le thread enfant est de le définir sur true. Le thread principal peut alors vérifier cette variable et, si elle est vraie, effectuer une jointure sur le thread enfant sans trop de blocage (le cas échéant).

Edit2 Si le thread se termine en raison d'une exception, alors avoir deux fonctions de thread "principales": La première a un try- à l' catchintérieur duquel il appelle la deuxième fonction de thread principal "réelle". Cette première fonction principale définit la variable "have_exited". Quelque chose comme ça:

bool thread_done = false;

void *thread_function(void *arg)
{
    void *res = nullptr;

    try
    {
        res = real_thread_function(arg);
    }
    catch (...)
    {
    }

    thread_done = true;

    return res;
}
Un mec programmeur
la source
1
Si telle est la définition de l'OP de «courir».
CashCow
Peut-être que vous m'avez mal compris. Je veux vérifier si un thread s'est correctement terminé ou non. Désolé pour le libellé peu clair.
kispaljr
7
Si différents threads lisent et écrivent thread_done, ce code est interrompu sans barrière de mémoire. Utilisez std::atomic<bool>plutôt.
ildjarn
1
Je ne faisais pas référence à plusieurs threads de travail, je faisais référence à un seul thread de travail écrivant boolpendant que le thread principal en lit - cela nécessite une barrière de mémoire.
ildjarn
3
Consultez cette question pour savoir pourquoi un std::atomic<bool>est nécessaire ici.
Robert Rüger
3

Ce mécanisme simple que vous pouvez utiliser pour détecter la finition d'un thread sans bloquer dans la méthode de jointure.

std::thread thread([&thread]() {
    sleep(3);
    thread.detach();
});

while(thread.joinable())
    sleep(1);
Evgeny Karpov
la source
2
détacher le thread n'est finalement pas ce que l'on veut, et si vous ne le faites pas, vous devez appeler join()depuis un thread qui n'attend pas que le thread perde sa joinable()propriété, sinon il bouclera sans fin (c'est-à-dire joinable()retourne true jusqu'à ce que le thread soit réellement join()ed et pas jusqu'à ce qu'il soit terminé)
Niklas R
joignable signifie qu'un fil tient une poignée de fil. si le thread est terminé, il sera toujours joignable. si vous avez besoin de vérifier sans attendre la fin du thread, voici la solution. Ce ne sont que quelques lignes de code. Pourquoi vous ne l'avez pas essayé en premier?
Evgeny Karpov
Je l'ai fait, et le point que j'essaie de faire valoir est que si vous supprimez la thread.detach()pièce, le programme ci-dessus ne se terminera jamais.
Niklas R
oui, ce ne sera pas le cas. c'est pourquoi cela appelle le détachement à la fin.
Evgeny Karpov
1
Cette méthode d'appel de détachement élimine le besoin de mutex et d'autres solutions plus complexes. Je l'utilise et ça marche! Merci d'avoir répondu.
septembre
1

Créez un mutex auquel le thread en cours d'exécution et le thread appelant ont tous deux accès. Lorsque le thread en cours d'exécution démarre, il verrouille le mutex, et lorsqu'il se termine, il déverrouille le mutex. Pour vérifier si le thread est toujours en cours d'exécution, le thread appelant appelle mutex.try_lock (). La valeur de retour de qui est le statut du thread. (Assurez-vous simplement de déverrouiller le mutex si le try_lock a fonctionné)

Un petit problème avec ceci, mutex.try_lock () retournera false entre le moment où le thread est créé et quand il verrouille le mutex, mais cela peut être évité en utilisant une méthode légèrement plus complexe.

Nathan Fox
la source
-1 Vous ne devez pas utiliser std::mutexpour ce type de signalisation (principalement en raison de la manière dont le mutex est généralement implémenté). Un atomic_flagfonctionne aussi bien dans ce cas avec beaucoup moins de frais généraux. A std::futurepourrait être encore mieux car il exprime plus clairement l'intention. De plus, rappelez-vous que cela try_lockpeut échouer faussement, donc le retour n'est pas nécessairement le statut du thread (bien que cela ne vous nuira probablement pas beaucoup dans ce cas particulier).
ComicSansMS
1

Vous pouvez toujours vérifier si l'id du thread est différent de la construction par défaut de std :: thread :: id (). Un thread en cours d'exécution a toujours un véritable identifiant associé. Essayez d'éviter trop de trucs fantaisistes :)

Michal Turlik
la source
0

Avoir sûrement une variable enveloppée de mutex initialisée sur false, que le thread définit truecomme la dernière chose qu'il fait avant de quitter. Est-ce assez atomique pour vos besoins?

Courses de légèreté en orbite
la source
1
Si vous utilisez quand même un mutex, alors je pense que ma solution (en utilisant uniquement le mutex, sans le booléen) est plus élégante. Si vous voulez absolument utiliser un booléen thread-safe, je recommanderais plutôt std :: atomic <bool>. Dans la plupart des implémentations, il sera sans verrouillage.
kispaljr
Pourquoi verrouiller du tout? Un fil ne lit que jamais, un n'écrit jamais. Et les écritures de taille de mot sont atomiques dans tous les cas IIRC.
Xeo
1
@Xeo: L'écriture peut être atomique, mais une barrière mémoire est toujours nécessaire si vous vous attendez à voir la valeur écrite sur un thread différent (qui peut s'exécuter sur un CPU différent). std::atomic<bool>s'occupe de cela pour vous, c'est pourquoi c'est la vraie réponse IMO.
ildjarn