Jak sprawdzić, czy nadal działa std :: thread?

84

Jak mogę sprawdzić, czy std::threadnadal działa (w sposób niezależny od platformy)? Brakuje mu timed_join()metody i joinable()nie jest do tego przeznaczona.

Myślałem o zablokowaniu muteksu za pomocą a std::lock_guardw wątku i użyciu try_lock()metody muteksu w celu określenia, czy nadal jest zablokowany (wątek działa), ale wydaje mi się to niepotrzebnie skomplikowane.

Czy znasz bardziej elegancką metodę?

Aktualizacja: Żeby było jasne: chcę sprawdzić, czy wątek został czysty, czy nie. W tym celu uważa się, że „wiszący” nić działa.

kispaljr
źródło
Wydaje mi się, że sprawdzenie, czy wątek nadal działa, ma znaczenie tylko wtedy, gdy się tego spodziewasz wait(), a jeśli tak, jeśli jeszcze tego nie zrobiłeś wait(), z definicji musi działać. Ale to rozumowanie może być niedokładne.
ereOn
Właściwie mam wątek, który wychodzi w wyjątkowych warunkach i chcę sprawdzić z głównego wątku, czy nadal działa, ale nie chcę czekać na (dołącz)
kispaljr
1
Co dokładnie masz na myśli mówiąc o biegu? Czy masz na myśli to, że aktywnie przetwarza, a nie w stanie oczekiwania, czy też masz na myśli wątek nadal istnieje i nie został zakończony?
CashCow
Zawsze możesz użyć doładowania :)
CashCow
4
Nie powinieneś akceptować odpowiedzi, jeśli nie byłeś z niej zadowolony.
Nicol Bolas

Odpowiedzi:

117

Jeśli chcesz korzystać z C ++ 11 std::asynci std::futurewykonywać swoje zadania, możesz skorzystać z wait_forfunkcji, std::futureaby sprawdzić, czy wątek nadal działa w zgrabny sposób:

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

Jeśli musisz użyć std::thread, możesz użyć, std::promiseaby uzyskać przyszły obiekt:

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

Oba te przykłady dadzą:

Thread still running

Dzieje się tak, ponieważ stan wątku jest sprawdzany przed zakończeniem zadania.

Ale z drugiej strony może być łatwiej po prostu zrobić to tak, jak inni już wspominali:

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

Edytować:

Istnieje również std::packaged_taskdo użycia z std::threadczystszym rozwiązaniem niż użycie 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
źródło
2
Niezła odpowiedź. Dodam, że działa też z wątkami bez zwracanej wartości i przyszłością <void>
kispaljr
Jaki jest powód tego kodu std::atomic<bool> done(false);? Czy boolatomowy nie jest domyślnie?
Hi-Angel,
6
@YagamyLight W C ++ nic nie jest domyślnie atomowe, chyba że jest opakowane w plik std::atomic. sizeof(bool)jest zdefiniowana w implementacji i może być> 1, więc możliwe jest, że nastąpi częściowy zapis. Jest też kwestia spójności pamięci podręcznej ...
Snps,
2
@YagamyLight Więcej informacji tutaj: Kiedy naprawdę muszę używać atomic <bool> zamiast bool?
Snps
1
zauważ, że std :: chrono_literals będzie wymagało C ++ 14 do kompilacji
Patrizio Bertoni
6

Łatwym rozwiązaniem jest posiadanie zmiennej boolowskiej, którą wątek ustawia na true w regularnych odstępach czasu, a która jest sprawdzana i ustawiana na false przez wątek, który chce znać status. Jeśli zmienna jest zbyt długa, to wątek nie jest już uważany za aktywny.

Bardziej bezpiecznym sposobem na wątki jest posiadanie licznika, który jest zwiększany przez wątek podrzędny, a wątek główny porównuje licznik z przechowywaną wartością, a jeśli to samo po zbyt długim czasie, wątek podrzędny jest uważany za nieaktywny.

Zauważ jednak, że w C ++ 11 nie ma sposobu, aby faktycznie zabić lub usunąć wątek, który się zawiesił.

Edytuj Jak sprawdzić, czy wątek został czysty, czy nie: Zasadniczo ta sama technika, co opisana w pierwszym akapicie; Miej zmienną logiczną zainicjowaną jako false. Ostatnią rzeczą, jaką robi wątek potomny, jest ustawienie wartości true. Wątek główny może następnie sprawdzić tę zmienną, a jeśli to prawda, wykonać złączenie w wątku potomnym bez większego (jeśli w ogóle) blokowania.

Edit2 Jeśli wątek kończy pracę z powodu wyjątku, mają dwie "główne" funkcje wątku: Pierwsza z nich ma try- catchwewnątrz której wywołuje drugą "prawdziwą" funkcję głównego wątku. Ta pierwsza główna funkcja ustawia zmienną „have_exited”. Coś takiego:

bool thread_done = false;

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

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

    thread_done = true;

    return res;
}
Jakiś koleś programista
źródło
1
Jeśli taka jest definicja „biegania” w PO.
CashCow
Może mnie źle zrozumiałeś. Chcę sprawdzić, czy wątek został czysty, czy nie. Przepraszamy za niejasne sformułowanie.
kispaljr
7
Jeśli czytają i piszą różne wątki thread_done, ten kod jest zepsuty bez bariery pamięci. Użyj std::atomic<bool>zamiast tego.
ildjarn
1
Nie odnosiłem się do wielu wątków roboczych, odnosiłem się do pojedynczego wątku roboczego piszącego do momentu bool, w którym główny wątek odczytuje z niego - to wymaga bariery pamięci.
ildjarn
3
Sprawdź to pytanie, aby omówić, dlaczego std::atomic<bool>jest tu konieczne.
Robert Rüger
3

Ten prosty mechanizm służy do wykrywania wykończenia gwintu bez blokowania metody łączenia.

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

while(thread.joinable())
    sleep(1);
Evgeny Karpov
źródło
2
odłączenie wątku ostatecznie nie jest tym, czego się chce, a jeśli tego nie zrobisz, musisz zadzwonić join()z jakiegoś wątku, który nie czeka, aż wątek straci swoją joinable()właściwość, w przeciwnym razie będzie pętli bez końca (tj. joinable()zwróci true, dopóki wątek nie będzie join()ed i nie do końca)
Niklas R
łączony oznacza, że ​​nić trzyma uchwyt nici. jeśli wątek jest zakończony, nadal będzie można go dołączyć. jeśli chcesz sprawdzić bez czekania na koniec wątku, oto rozwiązanie. To tylko kilka linijek kodu. Dlaczego nie spróbowałeś tego pierwszy?
Evgeny Karpov
Zrobiłem i próbuję wskazać, że jeśli usuniesz thread.detach()część, powyższy program nigdy się nie zakończy.
Niklas R
tak, nie będzie. dlatego w końcu nazywa się odłączaniem.
Evgeny Karpov
1
Ta metoda wywoływania detach eliminuje potrzebę stosowania muteksu i innych bardziej złożonych rozwiązań. Używam go i działa! Dziękuję za odpowiedź.
wrz
1

Utwórz muteks, do którego ma dostęp zarówno działający wątek, jak i wątek wywołujący. Kiedy uruchomiony wątek rozpoczyna się, blokuje muteks, a kiedy się kończy, odblokowuje muteks. Aby sprawdzić, czy wątek nadal działa, wątek wywołujący wywołuje mutex.try_lock (). Zwracaną wartością jest stan wątku. (Po prostu upewnij się, że odblokowałeś muteks, jeśli try_lock zadziałał)

Jeden mały problem z tym, mutex.try_lock () zwróci false między momentem utworzenia wątku a zablokowaniem muteksu, ale można tego uniknąć stosując nieco bardziej złożoną metodę.

Nathan Fox
źródło
-1 Nie powinieneś używać std::mutexdo tego rodzaju sygnalizacji (głównie z powodu zwykle implementacji muteksu). An atomic_flagdziała równie dobrze w tym przypadku, przy znacznie mniejszym obciążeniu. A std::futuremoże być nawet lepszy, ponieważ wyraźniej wyraża zamiar. Należy również pamiętać, że try_lockmoże się nie udać fałszywie, więc powrót jest nie koniecznie status wątku (choć prawdopodobnie nie zaszkodzi Ci dużo w tym konkretnym przypadku).
ComicSansMS,
1

Zawsze możesz sprawdzić, czy id wątku różni się od domyślnej konstrukcji std :: thread :: id (). Działający wątek ma zawsze autentyczny powiązany identyfikator. Staraj się unikać zbyt wielu wymyślnych rzeczy :)

Michał Turlik
źródło
0

Z pewnością należy zainicjować zmienną opakowaną w muteks false, na którą wątek ustawia się truejako ostatnia rzecz, jaką robi przed wyjściem. Czy to jest wystarczająco atomowe dla Twoich potrzeb?

Lekkość wyścigów na orbicie
źródło
1
Jeśli i tak używasz muteksu, uważam, że moje rozwiązanie (używając tylko muteksu, bez wartości logicznej) jest bardziej eleganckie. Jeśli absolutnie chcesz użyć wartości logicznej bezpiecznej dla wątków, poleciłbym zamiast tego std :: atomic <bool>. W większości wdrożeń nie będzie blokować.
kispaljr
Po co w ogóle blokować? Tylko jeden wątek czyta, tylko jeden pisze. W każdym przypadku zapisy według rozmiaru słowa są atomowe.
Xeo,
1
@Xeo: Zapis może być atomowy, ale bariera pamięci jest nadal potrzebna, jeśli spodziewasz się zobaczyć zapisaną wartość w innym wątku (który może być wykonywany na innym procesorze). std::atomic<bool>dba o to za Ciebie, dlatego jest to prawdziwa odpowiedź IMO.
ildjarn