Aplikacja wielowątkowa po kilku godzinach działania zawiesza się

Aplikacja wielowątkowa po kilku godzinach działania zawiesza się
TW
  • Rejestracja:około 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:5
0

Mam problem z aplikacją wielowątkową w Delphi 2009. Aplikacja działa pod kontrolą systemu operacyjnego Windows XP Professional.

Problemem jest to, że po kilku godzinach aplikacja się zawiesza i generowany jest błąd "Out of memory". Ogólnie w aplikacji wykorzystuję tworzenie wątków kilka razy i zamykanie ich. Oto fragment kodu źródłowego, który moim zdaniem wywołuje błąd:

Kopiuj
pom := TPomiarRE72Watek.Create(self);
  repeat until (SecondsBetween(Now, CzasPocz) > 3) or (Number_Bytes_Read > 0);
  if Number_Bytes_Read = 0 then
    TerminateThread(pom.Handle, 0);

Czyli czekam chwilę aż wątek się zakończy, a jeśli nie to przerywam go ręcznie.

Oto kod wątku:

Kopiuj
unit PomiarRE72Watek;

interface

uses
  Classes, Regulator_RE72;

type
  TPomiarRE72Watek = class(TThread)
  private
    FRegulator_RE72 : TRegulator_RE72;
  protected
    procedure Execute; override;
  public
    constructor Create(regulator : TREgulator_RE72);
  end;

implementation

uses Windows;

constructor TPomiarRE72Watek.Create(regulator : TREgulator_RE72);
begin
  inherited Create(False); // wywołanie wątku
  FRegulator_RE72 := regulator; // przypisanie wartości do zmiennej
end;

procedure TPomiarRE72Watek.Execute;
begin
  FreeOnTerminate := True;
  ReadFile(FRegulator_RE72.hCommDev, FRegulator_RE72.aBuforWej, FRegulator_RE72.iloscBitowDoOdczytu, FRegulator_RE72.Number_Bytes_Read, NIL);
end;

end.

Wychodzi więc na to, że jeżeli wątek się wykona to powinien zwolnić pamięć, a jeżeli nie to zakańczam wątek z głównego wątku. ten wątek uruchamiany jest co 2 minuty, więc może nie zwalnia tej pamięci i stąd powstaje błąd "out of memory" po pewnym czasie?

Proszę o sugestie.


Pozdrawiam!
Krzysiek (twicek)
edytowany 1x, ostatnio: twicek
KA
  • Rejestracja:ponad 19 lat
  • Ostatnio:3 minuty
  • Lokalizacja:Gorlice
2

Mało kodu ten kod wygląda na to że nie powinien powodować wycieku pamięci tak jak piszesz wątek zostanie zwolniony ale tak jak napisałem mało kodu i są niejasności:

  1. Czy klasa TPomiarRE72Watek nie tworzy gdzieś innych obiektów i ich nie zwalnia?
  2. Jak wygląda metoda ReadFile?
  3. Czy TREgulator_RE72 nie jest za każdym razem tworzona a nie zwalniana?

Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
TW
  • Rejestracja:około 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:5
0
  1. Czy klasa TPomiarRE72Watek nie tworzy gdzieś innych obiektów i ich nie zwalnia?
    Odp. Nie tworzy.

  2. Jak wygląda metoda ReadFile?
    Odp. Jest to standardowa metoda API zawarta w Delphi.

  3. Czy TREgulator_RE72 nie jest za każdym razem tworzona a nie zwalniana?
    Jest tworzona na początku działania programu, a następnie zwalniana przy wyłączaniu całego programu.

Tutaj jest log z MemoryManagera:

Kopiuj
FastMM has detected an error during a GetMem operation. FastMM detected that a block has been modified after being freed. 

Modified byte offsets (and lengths): 14(1)

The previous block size was: 76

This block was previously allocated by thread 0xFE8, and the stack trace (return addresses) at the time was:
40462B [System.pas][System][TObject.NewInstance][9804]
404B72 [System.pas][System][@ClassCreate][10591]
4DD5AF [PomiarRE72Watek.pas][PomiarRE72Watek][TPomiarRE72Watek.Create]
4EA47B [Regulator_RE72.pas][Regulator_RE72][TRegulator_RE72.OdczytajTemperatureZTermopary][156]
4E75B4 [Badanie.pas][Badanie][TBadanie.sprawdzCzyDobreTemperatury][582]
7C90E473 [KiUserCallbackDispatcher]
4E62A5 [Badanie.pas][Badanie][TBadanie.wykonajPrzebieg][226]
4E5C9B [BadanieWatek.pas][BadanieWatek][TBadanieWatek.Execute][118]
7C90E473 [KiUserCallbackDispatcher]
42F989 [Classes.pas][Classes][ThreadProc][10892]
7C90E473 [KiUserCallbackDispatcher]

The block was previously used for an object of class: TPomiarRE72Watek

The allocation number was: 225087761

The block was previously freed by thread 0xD40, and the stack trace (return addresses) at the time was:
40327E [System.pas][System][@FreeMem][3457]
404649 [System.pas][System][TObject.FreeInstance][9810]
404BBD [System.pas][System][@ClassDestroy][10632]
42FB7C [Classes.pas][Classes][TThread.Destroy][10972]
40468F [System.pas][System][TObject.Free][9829]
42F9E1 [Classes.pas][Classes][ThreadProc][10902]
40588A [System.pas][System][ThreadWrapper][13816]
7C80B729 [Unknown function at GetModuleFileNameA]

The current thread ID is 0xFE8, and the stack trace (return addresses) leading to this error is:
40C349 [FastMM4.pas][FastMM4][DebugGetMem][8733]
40462B [System.pas][System][TObject.NewInstance][9804]
404B72 [System.pas][System][@ClassCreate][10591]
4DD5AF [PomiarRE72Watek.pas][PomiarRE72Watek][TPomiarRE72Watek.Create]
4EA47B [Regulator_RE72.pas][Regulator_RE72][TRegulator_RE72.OdczytajTemperatureZTermopary][156]
4E75CE [Badanie.pas][Badanie][TBadanie.sprawdzCzyDobreTemperatury][582]
7C90E473 [KiUserCallbackDispatcher]
4E62A5 [Badanie.pas][Badanie][TBadanie.wykonajPrzebieg][226]
4E5C9B [BadanieWatek.pas][BadanieWatek][TBadanieWatek.Execute][118]
7C90E473 [KiUserCallbackDispatcher]
42F989 [Classes.pas][Classes][ThreadProc][10892]

Current memory dump of 256 bytes starting at pointer address 7F5A1E0:
74 61 4F 00 80 80 80 80 80 80 80 80 80 80 00 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 80 80 80 80 D6 E2 0B F4 80 80 80 80 80 80 80 80 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
t  a  O  .  €  €  €  €  €  €  €  €  €  €  .  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  Ö  â  .  ô  €  €  €  €  €  €  €  €  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

Pozdrawiam!
Krzysiek (twicek)
ER
  • Rejestracja:około 17 lat
  • Ostatnio:około 11 lat
2

TerminateThread() powoduje natychmiastowe zamknięcie wątku i nie powinno się stosować w normalnych sytuacjach. Funkcja może powodować access violation albo właśnie wyciek pamięci:

Windows Server 2003 and Windows XP: The target thread's initial stack is not freed, causing a resource leak.

Powinieneś użyć pom.Terminate, oraz zmodyfikować funkcję execute, tak aby w pętli plik czytany był fragmentami po x bajtów. A sprawdzając stan Terminated opuszczałbyś procedurę.

edytowany 2x, ostatnio: ergo
TW
  • Rejestracja:około 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:5
0

Spróbuję tak też zrobić. Ale z tego co obserwuję, to raczej wątek zawsze powinien się kończyć poprawnie i metoda TerminateThread() nie powinna się wywoływać. Czy jest możliwe, że zmienna pom dalej przechowuje referencję do niezwolnionego obszaru pamięci? Czy FreeOnTerminate nie zawsze działa?


Pozdrawiam!
Krzysiek (twicek)
ER
  • Rejestracja:około 17 lat
  • Ostatnio:około 11 lat
1

FreeOnTerminate powoduje, że jak już się zakończy funkcja Execute to wątek jest uwalniany, inaczej siedziałyby sobie w pamięci gotowy do działania. Gdzieś w wątku głównym można by sobie ponownie go uruchomić: pom.execute. Inaczej mówiąc, wywoływana jest metoda Free(); ale dopiero po zakończeniu działania wątku, po zakończeniu execute. A TerminateThread() natychmiast zatrzymuje wątek, tak że żaden kod w nim się już nie wykonuje.

Czy jest możliwe, że zmienna pom dalej przechowuje referencję do niezwolnionego obszaru pamięci

Zmienna pom wkazuje na utworzony obiekt TThread. I będzie na niego wskazywać dopóki tego nie zmienisz, nie wiem jaki masz kod i co w nim robisz.

abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
2

jeśli czytasz z coma to nie prościej użyć np. TComPort. Sprawdzony osobiście, działa, z wątkami też sobie bardzo ładnie radzi i ma kilka udogodnień, które mogą Ci się spodobać.
BTW jak już wspominał @ergo używanie TerminateThread to jest zły pomysł.
BTW2 jak chcesz żebyśmy coś konstruktywnego stwierdzili to daj więcej kodu


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
edytowany 2x, ostatnio: abrakadaber
TW
  • Rejestracja:około 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:5
0

Ogólnie chodzi mi o odczytanie danych z urządzenia po interfejsie MODBUS. Tylko problem polega na tym, że czasami urządzenie nie odpowiada, lub też występuję brak łączności i wtedy program bez wątków się zawiesza. Spróbuję użyć TComPort. Jak ktoś ma jeszcze jakieś sugestie do powyższych kwestii to będę wdzięczny.


Pozdrawiam!
Krzysiek (twicek)
ER
  • Rejestracja:około 17 lat
  • Ostatnio:około 11 lat
1

Nooo, jeżeli program Ci się wieszał na ReadFile to przecież w wątku będzie się dziać to samo. Postanowiłeś wstawić wątek i na siłę ubijać, zamiast rozwiązać problem z odczytem. I jak widzisz powoduje to błąd out of memory, bo jest za dużo żądań odczytu pliku:

The ReadFile function may fail with ERROR_INVALID_USER_BUFFER or ERROR_NOT_ENOUGH_MEMORY whenever there are too many outstanding asynchronous I/O requests.

Musisz ustawić timeout do odczytu strumienia danych: http://msdn.microsoft.com/pl-pl/library/windows/desktop/aa363437(v=vs.85).aspx

babubabu
  • Rejestracja:około 13 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Łódź
  • Postów:648
0

Może mój post nie jest dokładną odpowiedzią na pytanie ale zauważyłem ciekawą rzecz.

Przykładowy kod wątku

Kopiuj
procedure Watek.Execute;
begin
  repeat
    // kod
  until (terminated) or (//warunek zakonczenia watku)
end;

I jeśli nawet mam ustawione FreeOnTerminate na true i warunek zakończenia wątku zostanie spełniony lub z wątku głównego wywołane zostanie Watek.Terminate to i tak wątek w jakiś sposób będzie wisiał w pamięci. Nie wiem czemu tak się dzieje ale o dziwo jak dałem coś takiego:

Kopiuj
procedure Watek.Execute;
begin
  Running := true;
  repeat
    // kod
  until (terminated) or (//warunek zakonczenia watku)
  Running := false;
end;

A w wątku głównym czy w timerze czy w inny sposób

Kopiuj
if not running then FreeAndNil(Watek)

To wątek się normalnie kończył i zwalniał i nie powodował żadnych wycieków ani cudów.
Próbowałem też używać Watek.Terminated ale ta właściwość w moim programie działała jak chciała, a jak nie chciała to nie działała.

Nie pamiętam w jaki sposób ale jakoś udało mi się obejść te cuda z running, wykorzystać Terminated i nie mieć żadnych wycieków z pamięci oraz żadnych problemów z niepoprawnym zakończeniem wątku czy wręcz jego nie zakończeniem.

Wiem jedno, programowanie wielowątkowe dopóki się go nie zrozumie robi sieczkę z mózgu nawet jeśli przeczytało się miliard tutoriali i wszystko powinno działać to i tak nie działa i nie wiadomo dlaczego.

edytowany 1x, ostatnio: babubabu
Zobacz pozostałe 2 komentarze
ER
Pytanie w jaki sposób sprawdzasz, że wątek "wisi w pamięci". Bo wystarczy w klasie TThread rejestrować wywołanie metody destroy. Coś mam podejrzenia, że bawisz się wskaźnikiem w rodzaju " if assigned(watek) then", a ten wskaźnik nie ma prawa przyjąć wartości nil nawet gdy obiekt się zwolni.
pelsta
@babubabu Jakbyś sobie przypomniał jak sobie poradziłeś bez zmiennej pomocniczej to podziel się z nami :)
babubabu
@ergo Nie. Takich rzeczy nie robiłem. Jak do tej pory nigdy nie kożyształem z funkcji assigned. @pelsta Wątki i Synapse wykrycie odłączenia klienta po stronie serwera. Moje rozkminy ta temat wielowątkowości gdzie pojawił mi się ten problem.
ER
@babubabu: Ok. Krótko mówiąc, bo jest tam wiele błędów, źle zatrzymujesz wątek TSerwer. Nie wolno na wątku używać Free, bo to nawet nie zadziała. Trzeba ustawić Serwer.terminate; a potem zaczekać aż wątek się zakończy: WaitForSingleObject(Serwer.handle, INFINITE). Przez to w Twoim kodzie można uruchomić drugi wątek, zanim pierwszy się zakończy i stąd pewnie myślałeś że masz jakieś wycieki.
szopenfx
Mogę się mylić, ale jeśli zwolniony został obiekt a został wskaźnik na niego i nic nie zamazało tego obszaru pamięci to w sumie można jeszcze coś z niego odczytać/zapisać i może imitować istnienie, choć nie chce mi się tego sprawdzać teraz a na pewno tak jest z rekordami.
TW
  • Rejestracja:około 12 lat
  • Ostatnio:prawie 12 lat
  • Postów:5
0

Pomogło ustawienie timeout - jak na razie aplikacja działa wyśmienicie. Dziękuję wszystkim za pomoc!


Pozdrawiam!
Krzysiek (twicek)
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)