Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Delphi программирование > Общие вопросы Delphi
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 25.03.2021, 13:44   #1
Masam
Пользователь
 
Регистрация: 19.11.2009
Сообщений: 19
По умолчанию Утечка памяти в потоках + указатели.

Добрый день.

Дано:

Описание типов:
Код:
//Динамический массив
type
  TErrorsLog = array of string;

//Рабочий поток
type
  TThreadTest = class(TThread)
  private
    text: string;
    procedure SetLog;
  protected
    procedure Execute; override;
  public
    PErrorsLog: ^TErrorsLog; //Как корректно передать массив, что бы несколько потоков его могли заполнять?
    PSection: ^TCriticalSection;
    id: integer;
end;

//Управляющий поток
type
  TThreadMain = class(TThread)
  private
    ErrorsLog: TErrorsLog;
    Section: TCriticalSection;
    Tests : array of TThreadTest;
    procedure SetLog;
  protected
    procedure Execute; override;
  public
    id, n: integer;
end;
Тело рабочего потока:
Код:
//Тело рабочего потока
procedure TThreadTest.Execute;
var
  i: integer;
begin
  try
    i := 0;
    while not Terminated do
      begin
        i := i + 1;
        text := 'id: ' + inttostr(id) + ' # test: ' + IntToStr(i);
        SetLog;
        Sleep(1);
      end;
  finally
    //Dispose(PErrorsLog); - //если очищаю указатель, в управляющем потоке переменная "ErrorsLog" - становится пустой.
    //Dispose(PSection);
  end;
end;

procedure TThreadTest.SetLog;
var
  n : integer;
begin
  PSection^.Enter;
  n := Length(PErrorsLog^);
  SetLength(PErrorsLog^, n + 1);
  PErrorsLog^[n] := text;
  PSection^.Leave;
end;
Тело управляющего потока:
Код:
//Тело управляющего потока
procedure TThreadMain.Execute;
var
  i: integer;
begin
  try
    Section := TCriticalSection.Create;

    SetLength(ErrorsLog, 0);
    SetLength(Tests, n);
    for i := 0 to n - 1 do
      begin
        Tests[i] := TThreadTest.Create(true);
        Tests[i].Priority := tpNormal;

        Tests[i].id := i;
        Tests[i].PSection := @Section;
        Tests[i].PErrorsLog := @ErrorsLog;

        Tests[i].Resume;
      end;

    while not Terminated do
      begin
        Sleep(50);
      end;

    for i := 0 to n - 1 do
      begin
        Tests[i].Terminate;
        if Tests[i].Suspended then
          Tests[i].Resume;
        Tests[i].WaitFor;
      end;

    Synchronize(SetLog);

  finally
    for i := 0 to n - 1 do
      Tests[i].Free;

    SetLength(ErrorsLog, 0);
    SetLength(Tests, 0);

    Section.Destroy;
  end;
end;

procedure TThreadMain.SetLog;
var
  i, n: integer;
begin
  n := Length(ErrorsLog);
  for i := 0 to n - 1 do
    Form1.Memo1.Lines.Add(ErrorsLog[i]);
end;
Форма:
Код:
var
  Form1: TForm1;
  ThreadMain: TThreadMain;

implementation

{$R *.dfm}

//Кнопка старт записи в массив
procedure TForm1.Button1Click(Sender: TObject);
var
  i, n: integer;
begin
  Memo1.Lines.Clear;
  if ThreadMain = nil then
    begin
      ThreadMain := TThreadMain.Create(true);
      ThreadMain.Priority := tpNormal;
      ThreadMain.n := 1;
      ThreadMain.id := 1;
      ThreadMain.Resume;
    end;
end;

//Кнопка завершение записи в массив
procedure TForm1.Button2Click(Sender: TObject);
begin
  if ThreadMain <> nil then
    begin
      ThreadMain.Terminate;
      if ThreadMain.Suspended then
        ThreadMain.Resume;
      ThreadMain.WaitFor;
      ThreadMain.Free;
      ThreadMain := nil;
    end;
end;
При много кратном нажатии кнопок старта управляющего потока и его завершения с различными интервалами программа отъедает места под максимальный массив и не выгружает его, подскажите пожалуйста как правильно организовать передачу массива в дополнительные потоки и его наполнение и очистку?

Дополнительно: Если в дополнительных потоках устанавливаю очищаю указатель "Dispose(PErrorsLog);" на массив, то почему то переменная в управляющем потоке становится пустой?
О_о

Последний раз редактировалось Masam; 25.03.2021 в 16:35.
Masam вне форума Ответить с цитированием
Старый 25.03.2021, 15:24   #2
evg_m
Старожил
 
Регистрация: 20.04.2008
Сообщений: 5,526
По умолчанию

Цитата:
Код:
    PErrorsLog: ^TErrorsLog; //Как корректно передать массив, что бы несколько потоков его могли заполнять?
    PSection: ^TCriticalSection;
все объекты и динамические массивы уже указатели.
поэтому просто
Код:
    ErrorsLog: TErrorsLog; //Как корректно передать массив, что бы несколько потоков его могли заполнять?
    Section: TCriticalSection;
Код:
        Tests[i].id := i;
        Tests[i].Section := Section;
        Tests[i].ErrorsLog := ErrorsLog;
это и так будет один и тот же объект/массив хотя и записанный в множестве разных переменных.
и теперь никаких здесь Dispose;
передавать можно как угодно ЕСЛИ заполнять в критической секцией.

есть хорошее правило удалять должен тот кто создавал.
т.е. управляющий поток.
и тогда когда она ему больше не нужна.
программа — запись алгоритма на языке понятном транслятору
evg_m на форуме Ответить с цитированием
Старый 25.03.2021, 16:33   #3
Masam
Пользователь
 
Регистрация: 19.11.2009
Сообщений: 19
По умолчанию

Спасибо большое за ответ.

Тогда у меня еще ряд вопросов:
1) Имеет ли место тут утечка памяти или это такое поведение менеджера памяти?
К примеру я создаю отдельную кнопку:
Код:
procedure TForm1.Button3Click(Sender: TObject);
var
  ErrorsLog: TErrorsLog;
  i, n: integer;
begin
  Memo1.Lines.Clear;

  for i := 0 to 10000 do
    begin
      SetLength(ErrorsLog, i + 1);
      ErrorsLog[i] := 'test: ' + IntToStr(i + 1);
    end;

  n := Length(ErrorsLog);
  for i := 0 to n - 1 do
    Form1.Memo1.Lines.Add(ErrorsLog[i]);

  SetLength(ErrorsLog, 0);
end;
Тогда при запуске программы она занимает место 2МБ, после нажатия кнопки и до выполнения "SetLength(ErrorsLog, 0);" она занимает 3МБ, и после выполнения "SetLength(ErrorsLog, 0);", объем памяти не уменьшился.

2) Если я у рабочего потока создам дополнительные поля "массивы / объекты", нужно ли их очищать по завершению потока и достаточно ли будет из управляющего потока просто выполнить "Tests[i].Free;", не будет ли в данном случае утечки памяти?

Пример:
Код:
//Рабочий поток
type
  TThreadTest = class(TThread)
  private
    text: string;
    Array1: array of string;
    Array2: array of string;
    ....
О_о
Masam вне форума Ответить с цитированием
Старый 25.03.2021, 16:56   #4
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Цитата:
ThreadMain.Resume;
Почему пишете на старом Delphi?

CriticalSection:TCriticalSection - следует создавать глобально, а не в объекте. Она патчит код и при деструкторе восстанавливает, то что там было.

Цитата:
Critical sections must be global in scope so that they are available to all threads.
Цитата:
if ThreadMain.Suspended then
ThreadMain.Resume;
лишняя пара строчек Suspended и resume применяется отладчиком. Так что после первого запуска resume больше дёргать его не следует. Это архитектурный баг дельфи.
Вместо суспендит и ресуме вам надо будет менять в своем коде приоритет
ThreadMain.Priority
В данном примере это не критично. Просто на будщее.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .
Pavia вне форума Ответить с цитированием
Старый 25.03.2021, 16:59   #5
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Цитата:
Сообщение от Masam Посмотреть сообщение
Тогда при запуске программы она занимает место 2МБ, после нажатия кнопки и до выполнения "SetLength(ErrorsLog, 0);" она занимает 3МБ, и после выполнения "SetLength(ErrorsLog, 0);", объем памяти не уменьшился.
Это нормально строки для memo1 отпочковались(инстацирповались) и записались на верх кучи. А вы освободили те что посерёдке.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .
Pavia вне форума Ответить с цитированием
Старый 25.03.2021, 17:01   #6
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Цитата:
Сообщение от Masam Посмотреть сообщение
1) Имеет ли место тут утечка памяти или это такое поведение менеджера памяти?
Утечки следует смотреть в EurekaLog или madExcept или аналогичной программе.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .

Последний раз редактировалось Pavia; 25.03.2021 в 17:48.
Pavia вне форума Ответить с цитированием
Старый 25.03.2021, 17:32   #7
Pavia
Лис
Старожил
 
Аватар для Pavia
 
Регистрация: 18.09.2015
Сообщений: 2,409
По умолчанию

Цитата:
Сообщение от Masam Посмотреть сообщение
Спасибо большое за ответ.

Тогда у меня еще ряд вопросов:


2) Если я у рабочего потока создам дополнительные поля "массивы / объекты", нужно ли их очищать по завершению потока и достаточно ли будет из управляющего потока просто выполнить "Tests[i].Free;", не будет ли в данном случае утечки памяти?

Пример:
Код:
//Рабочий поток
type
  TThreadTest = class(TThread)
  private
    text: string;
    Array1: array of string;
    Array2: array of string;
    ....
Строки и массивы в Delphi автоматически освобождаются.

А вот объекты и динамическая переменные сколько раз их создал столько и надо удалить. Колличество конструкторов должно уравнять колличество деструкиеров. Free в переводе очистка. Она вызывает деструкторы и тот сам подчищает переменные управляемого рода такие как строки и динамические массивы.

Ещё раз повторю где память выделили в том объекте и освобождаете. Так ошибки проще искать.
Хорошо поставленный вопрос это уже половина ответа. | Каков вопрос, таков ответ.
У дзен программиста программа делает то что он хотел, а не то что он написал .
Pavia вне форума Ответить с цитированием
Старый 04.04.2021, 15:31   #8
Masam
Пользователь
 
Регистрация: 19.11.2009
Сообщений: 19
По умолчанию

Цитата:
Сообщение от evg_m Посмотреть сообщение
все объекты и динамические массивы уже указатели.
поэтому просто
Код:
    ErrorsLog: TErrorsLog; //Как корректно передать массив, что бы несколько потоков его могли заполнять?
    Section: TCriticalSection;
Код:
        Tests[i].id := i;
        Tests[i].Section := Section;
        Tests[i].ErrorsLog := ErrorsLog;
это и так будет один и тот же объект/массив хотя и записанный в множестве разных переменных.
и теперь никаких здесь Dispose;
передавать можно как угодно ЕСЛИ заполнять в критической секцией.

есть хорошее правило удалять должен тот кто создавал.
т.е. управляющий поток.
и тогда когда она ему больше не нужна.
К сожалению ответ не верный.

В указанном примере у меня не отработало заполнение общего массива в рабочем потоке, а изначально указанный через указатели - работает.
О_о
Masam вне форума Ответить с цитированием
Старый 04.04.2021, 15:33   #9
Masam
Пользователь
 
Регистрация: 19.11.2009
Сообщений: 19
По умолчанию

Цитата:
Сообщение от Pavia Посмотреть сообщение
Утечки следует смотреть в EurekaLog или madExcept или аналогичной программе.
Спасибо за ответ. Очень полезно.
О_о
Masam вне форума Ответить с цитированием
Старый 04.04.2021, 15:39   #10
Masam
Пользователь
 
Регистрация: 19.11.2009
Сообщений: 19
По умолчанию

Цитата:
Сообщение от Pavia Посмотреть сообщение
Почему пишете на старом Delphi?

CriticalSection:TCriticalSection - следует создавать глобально, а не в объекте. Она патчит код и при деструкторе восстанавливает, то что там было.




лишняя пара строчек Suspended и resume применяется отладчиком. Так что после первого запуска resume больше дёргать его не следует. Это архитектурный баг дельфи.
Вместо суспендит и ресуме вам надо будет менять в своем коде приоритет
ThreadMain.Priority
В данном примере это не критично. Просто на будщее.
Хм.. по вопросу версии делфи, у меня делфи XE3, да не новая версия, просто так сложилась что использую именно эту версию.

А по приоритетам не понял, судя по описанию самого низкого приоритета, поток продолжает работать, мне же важно что бы выполнение цикла приостанавливалось:
Код:
    while not Terminated do
      begin
        i := i + 1;
        text := 'id: ' + inttostr(id) + ' # test: ' + IntToStr(i);
        SetLog;
        Sleep(1);
      end;
Как более корректно это должно быть реализовано?
О_о
Masam вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Утечка памяти [Освобождение памяти массива] denis76560 Общие вопросы Delphi 4 27.11.2016 18:20
Утечка памяти OmegaBerkut Общие вопросы Delphi 21 09.04.2015 22:12
Утечка памяти :( batnik.com C++ Builder 1 09.05.2013 18:09
Утечка памяти Juffin Общие вопросы Delphi 3 02.11.2010 12:11
Утечка памяти ZvEr_HaCkEr Свободное общение 13 24.09.2010 19:30