Asynchroniczne wątki

Asynchroniczne wątki
bl4ster
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 6 lat
  • Lokalizacja:Lublin
  • Postów:197
0

Mam problem z uzyskaniem asynchroniczności wątków. Kod wygląda tak:
server.h:

Kopiuj
class Server{

public:
    void send(int time){
        std::async(std::launch::async, Server::work, time);

    }

    static void work(int time){
        std::this_thread::sleep_for(std::chrono::seconds(time));
        std::cout<<"Wątek "<<time<<" zakonczony\n";
    }
};

main.cpp:

Kopiuj
#include "server.h"

int main()
{
    Server().send(3);
    Server().send(2);
}

Na wyjściu dostaję:

Kopiuj
Wątek 3 zakonczony
Wątek 2 zakonczony

A chciałbym otrzymać:

Kopiuj
Wątek 2 zakonczony
Wątek 3 zakonczony

Gdzie robię błąd?


"Jesteśmy świadomymi istotami, a życie jest sposobem w jaki wszechświat poznaje sam siebie." prof. Brian Cox
edytowany 1x, ostatnio: flowCRANE
Prav
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 6 lat
  • Postów:25
3

Przy wyjściu ze scope destruktor std::future, zwracanego przez std::async, blokuje wątek aż do wykonania funkcji przekazanej do std::async. Możesz użyć std::thread().detach(), albo trzymać zwracane future w jakimś kontenerze:

Kopiuj
class Server {
public:
    std::vector<std::future<void>> futures;
    
    void send(int time) {
        auto f = std::async(std::launch::async, Server::work, time);
        futures.push_back(std::move(f));
    }
 
    static void work(int time) {
        std::this_thread::sleep_for(std::chrono::seconds(time));
        std::cout<<"Wątek "<<time<<" zakonczony\n";
    }
};
 
int main()
{
    Server server{};
    server.send(3);
    server.send(2);
}

Tylko pamiętaj, żeby usuwać te, które się już zakończyły.

edytowany 2x, ostatnio: Prav
MO
  • Rejestracja:około 10 lat
  • Ostatnio:około 20 godzin
  • Lokalizacja:Tam gdzie jest (centy)metro...
1

Nie trzeba odłączać wątków. wykonanie get() na futures wystarcza.
Nie masz gwarancji kolejności wykonania zleceń asynchronicznych bo nie masz kontroli nad planistą. Raczej zrób to w trybie RAII. Tu masz przykład:

Kopiuj
#include <future>
#include <iostream>
#include <vector>

class Server{
    std::vector<std::future<void>> ftrs; 
public:
    void send(int time) {
        auto ftr = std::async(std::launch::async, &Server::work, this, time);
        ftrs.push_back(std::move(ftr));
    }
 
    void work(int time) {
        std::this_thread::sleep_for(std::chrono::seconds(time));
        std::cout << "Wątek id = " << std::this_thread::get_id() << " z czasem = "
            << time << "s zakonczony\n";
    }
    ~Server() {
        std::cout << "Destructor ftrs size = " << ftrs.size() << '\n';
        for(auto& ftr: ftrs) {
            ftr.get();
        }
    }
};

int main()
{
    Server s;
    s.send(3);
    s.send(2);
}

Każdy problem w informatyce można rozwiązać, dodając kolejny poziom pośredniości,z wyjątkiem problemu zbyt dużej liczby warstw pośredniości — David J. Wheeler
bl4ster
  • Rejestracja:ponad 8 lat
  • Ostatnio:ponad 6 lat
  • Lokalizacja:Lublin
  • Postów:197
0

Zauważcie, że ja wywołuję funkcję send na dwóch różnych obiektach.
Jeśli wywołam ją na tym samym obiekcie, tak jak w waszych kodach, to działa poprawnie (nawet bez tworzenia vectora).


"Jesteśmy świadomymi istotami, a życie jest sposobem w jaki wszechświat poznaje sam siebie." prof. Brian Cox
MO
No to mała podpowiedź.. zostaw kod taki jak u Ciebie (czyli ze statycznym send() ) i dodaj informację o thread ID którą widzisz w moim. Jakieś wnioski? :-) No ale dobrze... chcesz mieć statycznie... zaraz wstawię...
MO
I jeszcze jedno pytanie.. jak myślisz .. co się tutaj dzieje... Server().send() z tym obiektem (Server()) który stworzyłeś tuż po zakończeniu linijki?
Prav
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 6 lat
  • Postów:25
1

Jeśli wywołam ją na tym samym obiekcie, tak jak w waszych kodach, to działa poprawnie (nawet bez tworzenia vectora).

Mam wrażenie, że jednak nie: https://wandbox.org/permlink/rIHGR1v92MvrdifD.
Nie wiem dlaczego chcesz tworzyć za każdym razem nowy obiekt, ale możesz zwyczajnie zwracać std::future z send() i trzymać je gdzieś indziej.

bl4ster
Masz rację, jednak zmieniłem ciało klasy Server i tego nie zauważyłem.
MO
  • Rejestracja:około 10 lat
  • Ostatnio:około 20 godzin
  • Lokalizacja:Tam gdzie jest (centy)metro...
1

Moim zdaniem niewygodne.. ale jak się upierasz... :

Kopiuj
#include <future>
#include <chrono>
#include <vector>
#include <iostream>

class Server{
    static std::vector<std::future<void>> ftrs; 
public:
    static void send(int time){
        auto ftr = std::async(std::launch::async, Server::work, time);
        ftrs.push_back(std::move(ftr));
    }
 
    static void work(int time){
        std::this_thread::sleep_for(std::chrono::seconds(time));
        std::cout<<"Wątek id = " << std::this_thread::get_id()
            << " z time = " << time <<"s zakonczony\n";
    }
    static void clean() {
        for(auto& ftr: ftrs) {
            ftr.get();
        }
    }
};

std::vector<std::future<void>> Server::ftrs;
 
int main()
{
    Server::send(3);
    Server::send(2);
    Server::clean();
}

Każdy problem w informatyce można rozwiązać, dodając kolejny poziom pośredniości,z wyjątkiem problemu zbyt dużej liczby warstw pośredniości — David J. Wheeler
MarekR22
po kiego grzyba te static? Uśnięcie ich czyni tą klasę o wiele bardziej użyteczną, a kod końcowy zapewne i tak nie będzie się niczym różnił.
MO
Bo się uparł. Wcześniej dostał bez static i w trybie RAII a teraz potworek :-/ Popatrz wyżej na wiadomość z wywołaniem metody w obiekcie już usuniętym.... tak więc nie jest tak źle :-/
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
0
bl4ster napisał(a):

Zauważcie, że ja wywołuję funkcję send na dwóch różnych obiektach.

Nie czaję, jakie to mam mieć znaczenie???

Jeśli wywołam ją na tym samym obiekcie, tak jak w waszych kodach, to działa poprawnie (nawet bez tworzenia vectora).

U mnie bez klas działa jak z klasami. Możesz podać dokładny kod, kiedy to działa jak sobie życzysz?

Wyrzuciłem klasy w ogóle, zrobiłem tak (dodałem get_id):

Kopiuj
#include <future>
#include <iostream>

void work(int time) {
    std::this_thread::sleep_for(std::chrono::seconds(time));
    std::cout<<"Wątek " << std::this_thread::get_id() << " -- " <<time<<" zakonczony\n";
}

void send(int time) {
    std::async(std::launch::async, work, time);
}

int main() {
    send(5);
    send(2);
}

i dostałem:

Kopiuj
Wątek 140679349307136 -- 5 zakonczony
Wątek 140679349307136 -- 2 zakonczony

-- ten sam id wątku.

Twoja wersja z dodanym id daje to samo (z dokładnością do numeru wątku):

Kopiuj
Wątek 140563016447744 -- 3 zakonczony
Wątek 140563016447744 -- 2 zakonczony

A w ogóle std::async nie musi chyba tworzyć nowego wątku, więc this_thread odwołuje się (i każe spać) wątkowi głównemu...

Zobacz pozostałe 2 komentarze
Prav
https://4programmers.net/Forum/1448845 zanim drugi std::async się wykona pierwszy już się zakończył i zwrócił wątek do poola.
koszalek-opalek
@Prav: Ok, dzięki, chyba widzę -- muszę więcej pobawić się wątkami... :)
MarekR22
ten kod pokazuje brak zrozumienia na czym polega tu problem. A problemem jest tu wartość zwracana future, co jest opisane wyżej.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.