Uruchamianie funkcji z kontrolą czasu

Uruchamianie funkcji z kontrolą czasu
pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:23 dni
  • Lokalizacja:Kraków
  • Postów:1055
1
  • Problem **: chcę napisać wrapper na dowolną funkcję, który to

  • wykonuje tę funkcję

  • mierzy czas jej wykonania

  • pobiera zwracaną przez nią wartość

  • kończy funkcję kiedy minie określony czas a ona sama się nie skończyła. Taki timeout, odpalam funkcję, ma ona określony czas na wykonanie, jeśli nie zdąży, to przerywam jej działanie. wrapper ma skończyć działanie i zwrócić rezultat w momencie, kiedy jeden z warunków 1) funkcja skończyła działanie i zwróciła wynik 2) osiągnięto timeout zostanie spełniony

  • Założenia **: mogę zrobić std::thread::detach() na wątku wykonującym funkcję, nie mogę zrobić na nim std::terminate(), nie mam dostępu do funkcji. To jest jakaś funkcja z jakimiś argumentami, pojęcia nie mam co dzieje się w środku i nie mogę tego modyfikować. Nie chciałbym używać czegoś ponad C++11.

  • Próby rozwiązania **:

Kopiuj
template <typename func_t, typename ... Args>
auto waiter (func_t func, const Args &... args) -> decltype(func(args...)) {
  const static std::chrono::milliseconds max_time(10);
  decltype(func(args...)) result;
  std::mutex mtx;
  std::unique_lock<std::mutex> lock(mtx);
  std::condition_variable cv;
  
    thread th([&] {
        result = func(args...);
        cv.notify_all();
    });

  auto waiting_result = cv.wait_for(lock, max_time);
  th.detach();
  if (waiting_result == std::cv_status::timeout) {
    throw std::runtime_error("timeout");
  }
  return result;
}

http://melpon.org/wandbox/permlink/4eopMBWg2RTfVtGC
niby wszystko działa dobrze, ale: po rzuceniu wyjątku std::runtime_error referencje przekazane do lambdy w std::thread::thread() są zabijane a funkcja działa dalej na nich. Mogę użyć std::shared_ptr, ale całość jest brzydka i nie dam sobie ręki uciąć, że nie ma tam 10 UB.
*

Kopiuj
template <typename func_t, typename ... Args>
auto waiter (func_t func, const Args &... args) -> decltype(func(args...)) {
  const static std::chrono::milliseconds max_time(10);
  auto handle = std::async(std::launch::async, func, args ...);
  if (handle.wait_for(max_time) == std::future_status::timeout) {
    throw std::runtime_error("timeout");
  } else {
    return handle.get();
  }
}

fajne i ładne, natomiast nie przerywa działania po timeout, bo w std::async() tworzy się std::thread a w std::~async() jest robione coś jak std::thread::join() na tym stworzonym wątku. Głupie. http://melpon.org/wandbox/permlink/Cyd2hYJCQSIxETqL
*

Kopiuj
template <typename func_t, typename ... Args>
auto waiter (func_t func, const Args &... args) -> decltype(func(args...)) {
  const static std::chrono::milliseconds max_time(10);
  auto task = std::packaged_task<decltype(func(args...))()>(std::bind(func, args...));
  auto handle = task.get_future();
  std::thread th(std::move(task));
  if (handle.wait_for(max_time) == std::future_status::timeout) {
  th.detach();
    throw std::runtime_error("timeout");
  } else {
    th.detach();
    return handle.get();
  }
}

to też działa http://melpon.org/wandbox/permlink/FoCdp6K8CnlmdJYi ale jestem przekonany, że robienie std::thread::detach() ma wątku z future to gwarantowane UB. Mam jednak wrażenie, że rozwiązanie poprawne jest najbliższe temu.


do not code, write prose
edytowany 6x, ostatnio: pingwindyktator
pingwindyktator
Chyba wciąż nie działa wołanie przy edycji postu, więc wołam @kq @satirev @Endrju @Drajwer @_13th_Dragon tutaj.
KA
a ja a ja?
pingwindyktator
@karolinaa no pochwal się wiedzą jak już tu się zapędziłaś ;D
KA
no uważam, że Endrju Gynvael i Shalom mają racje
Endrju
  • Rejestracja:około 22 lata
  • Ostatnio:ponad rok
4

Tak na szybko, to wydaje mi się, że w samym C++11 raczej się nie uda. Nie ma niczego co pozwala zakończyć wybrany wątek jeżeli nie ma on tego mechanizmu wbudowanego. Z oczywistych względów "dowolna funkcja" nie będzie miała tego mechanizmu. Można wykombinować coś, co spowoduje że ta funkcja zostanie olana (tzn. detach) ale to nie wygląda ładnie i może spowodować problemy później (np. przy zamykaniu programu mogą dziać się dziwne rzeczy, bo wątek nie będzie poprawnie kończony).

Możesz pobrać std::thread::native_handle() i użyć funkcji z implementacji wątków (np. pthread_cancel). Oczywiście to już nie jest sam C++11 i jest zależne od implementacji.


"(...) otherwise, the behavior is undefined".
edytowany 1x, ostatnio: Endrju
pingwindyktator
Mogę robić detach, nie jest to problemem w moim use case.
Endrju
No i jak potem zakończysz ten wątek? Jak main wyjdzie a ten wątek dalej będzie leciał, to mogą dziać się dziwne rzeczy. (UB zapewne w którym momencie/z jakiegoś powodu)
pingwindyktator
Ten wątek zakończy się na końcu całego programu czy też nie? Bo jak nie, to problem jest.
Endrju
Mogą dziać się dziwne rzeczy. System operacyjny ostatczenie wszystko posprząta, ale wcześniej możesz pooglądać jakieś dziwne błędy.
Gynvael Coldwind
  • Rejestracja:ponad 21 lat
  • Ostatnio:16 dni
  • Lokalizacja:Zurich, Switzerland
  • Postów:457
6

Ciekawy problem :)

Zgadzam się z @Endrju, to generalnie jest trudny problem zarówno w samym C++ jak i poza nim.

Normalnie (tj. w kodzie produkcyjnym) ten problem rozwiązuje się wbudowując w funkcje mechanizmy, które pozwalają na jej przedwczesne zakończenie (takie "cancel task").

Co do tego dlaczego to jest "trudny problem":
Generalnie wraper nie wie co ta funkcja zrobiła. A nuż zajęła jakiś ważny muteks? Otworzyła 1000 plików? Zaalokowała dynamicznie 1234 MB? Etc.
Więc zabicie wykonującego ją wątku niestety (w najgorszym wypadku) może spowodować ostry resource leak i doprowadzić do deadlocków.

Dlatego też przy funkcjach typu "terminate thread" są zazwyczaj uwagi typu (WinAPI):

TerminateThread is a dangerous function that should only be used in the most extreme cases.

Więc jak wyżej - najlepiej by było jeśli byś miał możliwość obudować taką funkcję w jakiegoś rodzaju wrapper, który umiałby jej przekazać informacje, że ma przerwać działanie, co niestety nie gra z Twoim założeniem :(

Alternatywnie: Czy możesz tą funkcje wywołać w oddzielnym procesie? C++ nie ma raczej wsparcia dla procesów, natomiast to rozwiązanie ma taką zaletę, że przy zabiciu procesu wszystkie zasoby są zwalniane, więc nie musisz się martwić leakami (a system-wide mechanizmy z którymi proces-dziecko by się ew. mógł komunikować zazwyczaj są odporne na śmierć dziecka; wiesz, typu system-wide mutex czy lock na plikach).


peace,
gynvael.coldwind//vx "Imagination is more important than knowledge..." Albert Einstein
Gynvael Coldwind
Dodam, że muteksy są zajmowane czasem w niekoniecznie oczywistych miejscach, typu przy inicjalizacji zmiennych typu static - "If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization." (chociaż z tego co kiedyś sprawdzałem, to kompilatory nie implementowały tego poprawnie - http://gynvael.coldwind.pl/?id=406)
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
3

Wydaje mi sie ze @Gynvael Coldwind ma tutaj rację w kontekście wykorzystania procesów zamiast wątków. Dodatkowo użycie procesów pozwala wprowadzić nawet mocniejszy sandbox np. z ograniczeniami pamięci. Ale tutaj niestety musiałbyś zdać sie na API docelowego systemu i nie byłoby to uniwersalne.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
pingwindyktator
  • Rejestracja:ponad 12 lat
  • Ostatnio:23 dni
  • Lokalizacja:Kraków
  • Postów:1055
0

@Gynvael Coldwind wiem o resources leak i dlatego nie mogę twardo wywołać std::terminate(). Wolałbym też nie używać niczego oprócz C++. Ewentualnie mogę użyć nawet C++17.
Co właściwie dzieje się z funkcją, na której zrobiłem detach()? Ona w środku coś robi, nie mogę nawet założyć, że kiedykolwiek się skończy. Przy return w main ten wątek zostanie zakończony i OS po nim posprząta? Jeśli tak, to detach() jest dobrym pomysłem.


do not code, write prose
Zobacz pozostałe 4 komentarze
Gynvael Coldwind
Tylko pamiętaj że shared_ptr nie jest thread-safe sam w sobie. I przy okazji możesz zdradzić jak się formatuje jako kod pojedyncze słowa w postach/komentarzach :)
Gynvael Coldwind
Test: std::cout (edit: uuu działa, thx!)
kq
@Gynvael Coldwind refcount shared_ptr jest atomic. Zakładam, że chodziło Ci o obiekt przez niego wskazywany - wtedy racja, ale w przykładzie od @pingwindyktator mamy typy thread-aware (condition variable, thread, mutex...), więc tylko o synchronizację rezultatu funkcji można by się martwić.
Gynvael Coldwind
@kq: Ah, racja, przecież każdy wątek ma swoją kopię shared_ptr, a dzielony ref count jest jak piszesz, atomic. My bad ;)
Endrju
  • Rejestracja:około 22 lata
  • Ostatnio:ponad rok
0

Zostanie zakończony. Ale po wyjściu z main program jest kończony (co się dzieje jest opisane w standardzie i również zapewne częściowo zależne od implementacji) i wątek może próbować robić coś, co już nie jest dozwolone. To może skończyć się segfaultem albo innym niespodziewanym błędem. To zależy co ta funkcja będzie robić.

Poczytaj np. tu: http://stackoverflow.com/questions/19744250/what-happens-to-a-detached-thread-when-main-exits


"(...) otherwise, the behavior is undefined".
edytowany 1x, ostatnio: Endrju
pingwindyktator
Nie rozumiem. Kiedy "wątek może próbować robić coś, co już nie jest dozwolone"? Po wyjściu z main?
Gynvael Coldwind
Generalnie jeśli mnie pamięć nie myli, to po wyjściu głównego wątku (tego w main()) są wywoływane wszystkie destruktory globalne (etc), więc jeśli jakiś nieświadomy, inny wątek odwoła się do obiektu, którego destruktor już został wywołany, to może być jak @Endrju napisał (segfault, etc).
pingwindyktator
No dobra, z braku laku muszę się tym nie przejmować. Niech działa i niech będzie segfault.
Endrju
Wyobraź sobie, że wątek używa std::cout. Gdzieś po wyjściu z main wywoła się jego destrutkor, a wątek nadal będzie próbował pisać. (Zob. też 3.6.3/4)
pingwindyktator
No wiem, wiem, wszystko rozumiem.
MO
  • Rejestracja:prawie 10 lat
  • Ostatnio:około 11 godzin
  • Lokalizacja:Tam gdzie jest (centy)metro...
0

Zerknij do <future> w C++. Uruchamia zadanie i posiada wait_for() oraz wait_until().
http://en.cppreference.com/w/cpp/thread/future/wait_for

Tylko nie wiem czy dokładnie o to Ci chodziło.


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
edytowany 1x, ostatnio: Mokrowski
pingwindyktator
Dokładnie to zrobiłem w rozwiązaniu 2. I wyjaśniłem, dlaczego nie działa
MO
Ok, masz rację.... Moje tygodniowe zmęczenie... :-)
2

Podsumuję to o czym rozmawialiśmy na ircu i na koniec dopiszę jeszcze jeden pomysł ; >

Odnośnie wrzuconych kodów:

  1. Jest względnie ok, poza tym, że po zawołaniu detach na th() i wywołaniu destruktora th możesz w odczepionym wątku działać na referencjach do nieistniejących obiektów. Dlatego też powinieneś w capture list przyjmować dane przez kopię [=]. Dalej zostaje kwestia odczepionego wątku (który nadal działa w tle). Wychodząc z głównego wątku podczas, gdy działa jeszcze jakiś inny wątek to UB.

  2. Tutaj masz rację, bo destruktor std::future zwróconego przez std::async czeka na zakończenie się wątku.

  3. Podobnie jak w pierwszym przypadku. Zakładając, że będziesz przyjmował dane przez kopie (wyłączywszy potencjalny UB przy wyjściu z maina) to powinno być ok. std::bind domyślnie przyjmuje dane przez kopię więc nic nie musisz zmieniać ; >

Pomimo tego, że te kody mają szanse zadziałać to żaden z nich nie będzie robił tego co chciałbyś żeby robił, tzn. detach powoduje tylko to, że *this nie jest już właścicielem wątku. Sam wątek nadal będzie wykonywany w tle. Gdybyś chciał jednak przerywać pracę wątku to musiałbyś albo ograniczyć się do wybranych platform, które udostępniają pthread_cancel albo przejść na model z procesami. O tym wszystkim wspominali już przedmówcy. Gdybyś mógł wymusić na callable cykliczne sprawdzanie interruption point to sprawa była prosta. No ale raczej nie chcesz/nie możesz przyjąć takiego założenia. W każdym razie dla zainteresowanych wspomnę, że ładne przykłady jak coś takiego zrobić (bezpiecznie) znajdziecie w książce Anthonego Williamsa C++ Concurrency in Action.

Na koniec podrzucę Ci jeszcze jeden pomysł - użycie klasy task_group z ITBB. Co prawda ITBB nie służy raczej do tego typu tasków, o których mowa w temacie ale wydaje się, że ma to szanse zadziałać dla Twojego problemu.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)