Wie überprüfe ich, ob noch ein std :: thread läuft?

84

Wie kann ich überprüfen, ob a std::threadnoch ausgeführt wird (plattformunabhängig)? Es fehlt eine timed_join()Methode und joinable()ist nicht dafür gedacht.

Ich dachte daran, einen Mutex mit einem std::lock_guardim Thread zu sperren und mithilfe der try_lock()Mutex-Methode festzustellen, ob er noch gesperrt ist (der Thread läuft), aber er scheint mir unnötig komplex zu sein.

Kennen Sie eine elegantere Methode?

Update: Um klar zu sein: Ich möchte überprüfen, ob der Thread sauber beendet wurde oder nicht. Zu diesem Zweck wird ein "hängender" Thread als laufend betrachtet.

kispaljr
quelle
Ich denke, zu überprüfen, ob ein Thread noch läuft, ist nur dann von Bedeutung, wenn Sie es erwarten, wait()und wenn ja, wenn Sie noch nicht wait()dafür gearbeitet haben, muss er per Definition ausgeführt werden. Aber diese Argumentation könnte ungenau sein.
Uhr
Eigentlich habe ich einen Thread, der unter außergewöhnlichen Bedingungen beendet wird, und ich möchte vom Haupt-Thread aus prüfen, ob er noch läuft, aber nicht darauf warten (beitreten)
kispaljr
1
Was genau meinst du mit Laufen? Meinen Sie damit, dass es aktiv verarbeitet wird und sich nicht in einem Wartezustand befindet, oder meinen Sie, dass der Thread noch vorhanden ist und nicht beendet wurde?
CashCow
Sie könnten immer Boost verwenden :)
CashCow
4
Sie hätten eine Antwort nicht akzeptieren sollen, wenn Sie damit nicht zufrieden wären.
Nicol Bolas

Antworten:

115

Wenn Sie bereit sind, C ++ 11 zu verwenden std::asyncund std::futureIhre Aufgaben auszuführen, können Sie die wait_forFunktion verwenden, um std::futurezu überprüfen, ob der Thread noch ordnungsgemäß ausgeführt wird:

#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.
}

Wenn Sie verwenden müssen std::thread, können Sie verwenden std::promise, um ein zukünftiges Objekt zu erhalten:

#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.
}

Beide Beispiele geben Folgendes aus:

Thread still running

Dies liegt natürlich daran, dass der Thread-Status vor Abschluss der Aufgabe überprüft wird.

Andererseits könnte es einfacher sein, es einfach so zu machen, wie andere es bereits erwähnt haben:

#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.
}

Bearbeiten:

Es gibt auch die std::packaged_taskVerwendung std::threadfür eine sauberere Lösung als die Verwendung von 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
quelle
2
Gute Antwort. Ich würde hinzufügen, dass es auch mit Threads ohne Rückgabewert und Zukunft <void>
funktioniert
Was ist der Grund für diesen Code std::atomic<bool> done(false);? Ist das boolAtom nicht standardmäßig?
Hi-Angel
6
@YagamyLight In C ++ ist standardmäßig nichts atomar, es sei denn, es ist in eine eingeschlossen std::atomic. sizeof(bool)Ist die Implementierung definiert und kann> 1 sein, so ist es möglich, dass ein teilweises Schreiben auftritt. Es gibt auch das Problem der Cache-Kohärenz.
Snps
2
@YagamyLight Weitere Informationen hier: Wann muss ich wirklich atomares <bool> anstelle von bool verwenden?
Snps
1
Beachten Sie, dass für die Kompilierung von std :: chrono_literals C ++ 14 erforderlich ist
Patrizio Bertoni
5

Eine einfache Lösung besteht darin, eine boolesche Variable zu haben, die der Thread in regelmäßigen Abständen auf true setzt und die von dem Thread, der den Status wissen möchte, überprüft und auf false gesetzt wird. Wenn die Variable zu lange falsch ist, wird der Thread nicht mehr als aktiv betrachtet.

Eine thread-sicherere Möglichkeit besteht darin, einen Zähler zu haben, der vom untergeordneten Thread erhöht wird, und der Haupt-Thread vergleicht den Zähler mit einem gespeicherten Wert. Wenn dieser nach zu langer Zeit derselbe ist, wird der untergeordnete Thread als nicht aktiv betrachtet.

Beachten Sie jedoch, dass es in C ++ 11 keine Möglichkeit gibt, einen hängengebliebenen Thread tatsächlich zu beenden oder zu entfernen.

Bearbeiten So überprüfen Sie, ob ein Thread sauber beendet wurde oder nicht: Grundsätzlich dieselbe Technik wie im ersten Absatz beschrieben; Lassen Sie eine boolesche Variable auf false initialisieren. Das Letzte, was der untergeordnete Thread tut, ist, ihn auf true zu setzen. Der Hauptthread kann dann diese Variable überprüfen und, falls true, einen Join für den untergeordneten Thread ausführen, ohne dass viel (falls vorhanden) blockiert wird.

Edit2 Wenn der Thread aufgrund einer Ausnahme beendet wird, haben Sie zwei "Haupt" -Funktionen des Threads: Die erste hat eine try-, catchin der die zweite "echte" Haupt-Thread-Funktion aufgerufen wird. Diese erste Hauptfunktion setzt die Variable "have_exited". Etwas wie das:

bool thread_done = false;

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

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

    thread_done = true;

    return res;
}
Ein Programmierer
quelle
1
Wenn dies die Definition des OP für "Laufen" ist.
CashCow
Vielleicht hast du mich falsch verstanden. Ich möchte überprüfen, ob ein Thread sauber beendet wurde oder nicht. Entschuldigung für den unklaren Wortlaut.
Kispaljr
7
Wenn verschiedene Threads lesen und schreiben thread_done, wird dieser Code ohne Speicherbarriere unterbrochen. Verwenden Sie std::atomic<bool>stattdessen.
ildjarn
1
Ich bezog mich nicht auf mehrere Worker-Threads, sondern auf einen einzelnen Worker-Thread, der auf das schreibt, boolwährend der Haupt-Thread daraus liest - dies erfordert eine Speicherbarriere.
ildjarn
3
Schauen Sie sich diese Frage an, um zu diskutieren, warum hier a std::atomic<bool>erforderlich ist.
Robert Rüger
3

Mit diesem einfachen Mechanismus können Sie die Fertigstellung eines Threads erkennen, ohne die Join-Methode zu blockieren.

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

while(thread.joinable())
    sleep(1);
Evgeny Karpov
quelle
2
Das Trennen des Threads ist schließlich nicht das, was man will, und wenn Sie dies nicht tun, müssen Sie join()von einem Thread aus aufrufen , der nicht darauf wartet, dass der Thread seine joinable()Eigenschaft verliert, da er sonst endlos wiederholt wird (dh joinable()true zurückgibt, bis der Thread tatsächlich ist join()ed und nicht bis es fertig ist)
Niklas R
Verbindbar bedeutet, dass ein Faden einen Fadengriff hält. Wenn der Thread fertig ist, kann er weiterhin verbunden werden. Wenn Sie überprüfen müssen, ohne auf das Ende des Threads zu warten, finden Sie hier die Lösung. Es sind nur ein paar Codezeilen. Warum hast du es nicht zuerst versucht?
Evgeny Karpov
Ich habe es getan, und der Punkt, den ich versuche, ist, dass thread.detach()das obige Programm niemals beendet wird, wenn Sie das Teil entfernen .
Niklas R
Ja, das wird es nicht. Deshalb nennt man es am Ende das Ablösen.
Evgeny Karpov
1
Diese Methode zum Aufrufen von Detach verzichtet auf Mutex und andere komplexere Lösungen. Ich benutze es und es funktioniert! Danke für die Antwort.
sep
1

Erstellen Sie einen Mutex, auf den sowohl der laufende Thread als auch der aufrufende Thread Zugriff haben. Wenn der laufende Thread startet, wird der Mutex gesperrt, und wenn er endet, wird der Mutex entsperrt. Um zu überprüfen, ob der Thread noch ausgeführt wird, ruft der aufrufende Thread mutex.try_lock () auf. Der Rückgabewert davon ist der Status des Threads. (Stellen Sie einfach sicher, dass Sie den Mutex entsperren, wenn try_lock funktioniert hat.)

Ein kleines Problem dabei ist, dass mutex.try_lock () zwischen dem Erstellen des Threads und dem Sperren des Mutex false zurückgibt. Dies kann jedoch mit einer etwas komplexeren Methode vermieden werden.

Nathan Fox
quelle
-1 Sie sollten std::mutexdiese Art der Signalisierung nicht verwenden (hauptsächlich aus Gründen, wie Mutex normalerweise implementiert wird). Ein atomic_flagfunktioniert in diesem Fall genauso gut mit viel weniger Aufwand. A std::futurekönnte sogar noch besser sein, da es die Absicht klarer ausdrückt. Denken Sie auch daran, dass dies try_lockmöglicherweise fälschlicherweise fehlschlägt, sodass die Rückgabe nicht unbedingt dem Status des Threads entspricht (obwohl Sie in diesem speziellen Fall wahrscheinlich nicht sehr verletzt werden).
ComicSansMS
0

Sicherlich muss eine mit Mutex umschlossene Variable initialisiert werden false, auf die der Thread trueals letztes festgelegt wird, bevor er beendet wird. Ist das atomar genug für Ihre Bedürfnisse?

Leichtigkeitsrennen im Orbit
quelle
1
Wenn Sie trotzdem einen Mutex verwenden, empfinde ich meine Lösung (nur den Mutex ohne Booleschen Wert) als eleganter. Wenn Sie unbedingt einen thread-sicheren Booleschen Wert verwenden möchten, würde ich stattdessen std :: atomic <bool> empfehlen. In den meisten Implementierungen ist es sperrenfrei.
Kispaljr
Warum überhaupt sperren? Ein Thread liest immer nur, einer schreibt immer nur. Und wortgroße Schreibvorgänge sind in jedem Fall atomar IIRC.
Xeo
1
@Xeo: Der Schreibvorgang kann atomar sein, es wird jedoch weiterhin eine Speicherbarriere benötigt, wenn Sie erwarten, dass der geschriebene Wert auf einem anderen Thread angezeigt wird (der möglicherweise auf einer anderen CPU ausgeführt wird). std::atomic<bool>kümmert sich darum für Sie, weshalb es die eigentliche Antwort IMO ist.
ildjarn
0

Sie können jederzeit überprüfen, ob sich die ID des Threads von der Standardkonstruktion std :: thread :: id () unterscheidet. Einem laufenden Thread ist immer eine echte ID zugeordnet. Versuche zu viel ausgefallenes Zeug zu vermeiden :)

Michal Turlik
quelle