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

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:

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:

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.

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?
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:

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  .  €  €  €  €  €  €  €  €  €  €  .  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  €  €  €  €  Ö  â  .  ô  €  €  €  €  €  €  €  €  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
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ę.

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?

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.

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

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.

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

0

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

Przykładowy kod wątku

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:

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

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.

0

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

1 użytkowników online, w tym zalogowanych: 0, gości: 1