ITask - значения параметров для анонимной процедуры

Мне нужно создать определенное количество iTasks для выполнения операций с динамическим массивом и другими полями в записи. Каждая iTask работает в определенной части этого массива. Массив - это поле в записи, которое передается как параметр var в iTask.

Операции в поле массива идут хорошо, однако другие поля записи не возвращают никакого значения после того, как все задачи завершают свою работу. Мне помог Далия в другом вопросе, который работает только с массивом, и это сработало, но теперь у меня проблемы с другими полями.

Это мой код:

program ProjectTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.Threading;

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

// (1) ===> here is the major procedure that populates the dyn. array and
// calculates other two fields  :  myrec.total  and myrec.avg

procedure ProcA(const pin, pfin: integer; var Prec: myrec);
var
  vind: integer;
begin
  for vind := pin to pfin do
    begin
      Prec.vet[vind] := vind * 10;
      Prec.total := Prec.total + Prec.vet[vind];        // sum all array values
    end;
  Prec.average := Trunc(Prec.total / Length(Prec.vet)); // calculates the average

end;

// (2)  Here iTask is created and calls ProcA

function CreateTask(first, last: integer; var Pmyrec: myrec): ITask;

var
  mylocalrec: myrec;

begin
  mylocalrec := Pmyrec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec)
    end);
end;

procedure Test;
var
  Recarray: myrec;
  Ptasks: array of ITask;
  vind, indtask, vslice: integer;
  vfirst, vlast, vthreads, vsize: integer;
begin

  vthreads := 4;
  vsize := 16;

  SetLength(Ptasks, vthreads);
  SetLength(Recarray.vet, vsize);

  // Initialize the array , just to check after iTask execution
  for vind := low(Recarray.vet) to high(Recarray.vet) do
    Recarray.vet[vind] := -33;

  // initialize the sum and average field just to check after iTask execution
  Recarray.total := -1;
  Recarray.average := -2;

  // portion of array to scan for each iTask
  vslice := Length(Recarray.vet) div vthreads;

  for indtask := low(Ptasks) to high(Ptasks) do
    begin
      vfirst := indtask * vslice;
      vlast := (indtask + 1) * vslice - 1;

      if (Length(Recarray.vet) mod vthreads <> 0) and (indtask = high(Ptasks)) then vlast := high(Recarray.vet);

      Ptasks[indtask] := CreateTask(vfirst, vlast, Recarray);
    end;

  // Starting all Tasks
  for indtask := low(Ptasks) to high(Ptasks) do
    Ptasks[indtask].Start;

  // Waits for all Tasks been concluded
  TTask.WaitForAll(Ptasks);

  // (3) Here it is listed the array contents and it is ok
  for vind := low(Recarray.vet) to high(Recarray.vet) do
      Writeln(' Array position  : ' + Format('%.3d', [vind]) + '   content  : ' + Recarray.vet[vind].tostring);

  Writeln(' =========================================================');

// (4) Here is is listed fields recarray.total and recarray.avg and they were not
// processed inside the iTask .    I expected to see the computed values for those fields

  Writeln(' Array sum   : ' + Format('%.0d', [Recarray.total]) + '    Array average   : ' + Format('%5.2n', [Recarray.average * 1.0]));

end;

begin
  Test;
  Readln;
end.

И вывод:

 Array position  : 000   content  : 0
 Array position  : 001   content  : 10
 Array position  : 002   content  : 20
 Array position  : 003   content  : 30
 Array position  : 004   content  : 40
 Array position  : 005   content  : 50
 Array position  : 006   content  : 60
 Array position  : 007   content  : 70
 Array position  : 008   content  : 80
 Array position  : 009   content  : 90
 Array position  : 010   content  : 100
 Array position  : 011   content  : 110
 Array position  : 012   content  : 120
 Array position  : 013   content  : 130
 Array position  : 014   content  : 140
 Array position  : 015   content  : 150
 =========================================================
 Array sum   : -1    Array average   : -2,00

Проблема в следующем: после запуска всех iTasks только поле динамического массива recarray.vet содержит правильные значения. Поля recarray.total и recarray.average по-прежнему содержат свои начальные значения, как и до запуска iTask.

Как правильно обновить значения в этих полях, чтобы они содержали правильные значения после завершения выполнения задач?

Попробуйте вычислить среднее и общее значение после WaitForAll, иначе вам придется иметь дело с коллизией и реализовать TInterlocked для этих значений. Тем не менее, я не уверен, как вы вообще получаете какое-либо значение, должно быть, магия компилятора. Также никто не может запустить вашу демонстрацию, поэтому никто не сможет дать вам проверенный совет.

FredS 12.07.2018 04:39

@Freds, я думаю, что опубликованный мной код готов, я могу скомпилировать и запустить его в обычном режиме, и я получаю результаты в поле динамического массива myrec.vet. Вы пробовали это? Почему ты сказал, что никто не может запустить мою демонстрацию?

JRG 12.07.2018 19:30

Как создать минимальный, полный и проверяемый пример stackoverflow.com/help/mcve

FredS 12.07.2018 20:00

@ FredS, спасибо за помощь. Я дополнил код, удалил то, что было ненужным, и добавил комментарии в ключевые части, связанные с проблемой и тем, что ожидалось в результате. Надеюсь, теперь кто угодно сможет воспроизвести его и дать несколько советов. Спасибо.

JRG 12.07.2018 20:25

Что ж, вы потерпели неудачу, я вижу "{$ R * .dfm}" и нет файла DFM. Но на самом деле это должно быть простое консольное приложение. Затем все, что нужно, - это вставить его в блокнот и изменить расширение на DPR.

FredS 12.07.2018 20:35

@FredS, если я хорошо понял, нужно ли мне копировать и вставлять код .dfm в этот вопрос?

JRG 12.07.2018 21:09

@JRG, консольному приложению не нужна визуальная форма. Но подумайте о том, чтобы пересмотреть свою стратегию здесь. Как сказал FredS в своем первом комментарии, общие и средние поля используются в одной записи для всех потоков. Это вызовет агонию только в том случае, если потоки не смогут одновременно работать с одними и теми же полями. Лучше позволить потокам возвращать эти значения отдельно и обрабатывать сводку по завершении потоков.

LU RD 12.07.2018 22:27

@LU RD, спасибо за пояснения. Я понял, что потоки пытаются одновременно обновить одно и то же поле. Я подумаю по-другому, чтобы вычислить эти значения! Спасибо всем за помощь и указания!

JRG 12.07.2018 23:07

Я отредактировал ваш вопрос, чтобы упростить ваш код и сделать правильный минимальный воспроизводимый пример. для тестирования такого кода не нужно задействовать формы. Вы можете создать простое консольное приложение, и все будет в одном файле.

Dalija Prasnikar 13.07.2018 11:15

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

Dalija Prasnikar 13.07.2018 11:18

@Dalija, отличная помощь в редактировании моего кода, большое спасибо! Я изучу больше о консольном приложении, чтобы сделать мои вопросы более понятными и облегчить получение помощи. Я тоже буду ждать ваших комментариев!

JRG 13.07.2018 20:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
11
542
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Мои 5 минут после ужина пищеварительный :)

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.SyncObjs,
  System.Threading;

type
  Pmyrec = ^Tmyrec;
  Tmyrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

// (1) ===> here is the major procedure that populates the dyn. array and
// calculates other two fields  :  Tmyrec.total  and Tmyrec.avg

procedure ProcA(const pin, pfin: integer; const Prec: Pmyrec);
var
  vind: integer;
begin
  for vind := pin to pfin do
    begin
      Prec.vet[vind] := vind * 10;
      TInterlocked.Add(Prec.total, Prec.vet[vind]);     // sum all array values
    end;
end;

// (2)  Here iTask is created and calls ProcA

function CreateTask(first, last: integer; const Pmyrec: Pmyrec): ITask;
begin
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, Pmyrec)
    end);
end;

procedure Test;
var
  Recarray: Tmyrec;
  Ptasks: array of ITask;
  vind, indtask, vslice: integer;
  vfirst, vlast, vthreads, vsize: integer;
begin

  vthreads := 4;
  vsize := 16;

  SetLength(Ptasks, vthreads);
  SetLength(Recarray.vet, vsize);

  // Initialize the array , just to check after iTask execution
  for vind := low(Recarray.vet) to high(Recarray.vet) do
    Recarray.vet[vind] := -33;

  // initialize the sum and average field just to check after iTask execution
  Recarray.total := -1;
  Recarray.average := -2;

  // portion of array to scan for each iTask
  vslice := Length(Recarray.vet) div vthreads;

  for indtask := low(Ptasks) to high(Ptasks) do
    begin
      vfirst := indtask * vslice;
      vlast := (indtask + 1) * vslice - 1;

      if (Length(Recarray.vet) mod vthreads <> 0) and (indtask = high(Ptasks)) then vlast := high(Recarray.vet);

      Ptasks[indtask] := CreateTask(vfirst, vlast, @Recarray);
    end;

  // Starting all Tasks
  for indtask := low(Ptasks) to high(Ptasks) do
    Ptasks[indtask].Start;

  // Waits for all Tasks been concluded
  TTask.WaitForAll(Ptasks);
  Recarray.average := Trunc(Recarray.total / Length(Recarray.vet)); // calculates the average


  // (3) Here it is listed the array contents and it is ok
  for vind := low(Recarray.vet) to high(Recarray.vet) do
      Writeln(' Array position  : ' + Format('%.3d', [vind]) + '   content  : ' + Recarray.vet[vind].tostring);

  Writeln(' =========================================================');

// (4) Here is is listed fields recarray.total and recarray.avg and they were not
// processed inside the iTask .    I expected to see the computed values for those fields

  Writeln(' Array sum   : ' + Format('%.0d', [Recarray.total]) + '    Array average   : ' + Format('%5.2n', [Recarray.average * 1.0]));

end;

begin
  Test;
  Readln;
end.

спасибо за Ваш ответ ! Отличная точка - TInterlocked.Add. Я изучил это и кажется правильным выбором в целом! Насчет того, чтобы вычислить среднее значение после выполнения всех задач, имеет смысл! Я тоже улучшу код вашими советами! Спасибо !

JRG 14.07.2018 19:00
Ответ принят как подходящий

Хотя кажется, что ваш код имеет только одну проблему - как заполнить целочисленные поля в записи задачами - когда вы решите эту задачу, у вас возникнет другая проблема - чтение и запись одного и того же места в памяти из нескольких потоков.

1. Как заполнить целочисленные поля в записи?

Записи являются типами значений, а динамические массивы - ссылочными типами. Это причина, по которой ваш код может обновлять значения в массиве, но не может обновлять значения в записи.

Посмотрим, что здесь происходит.

function CreateTask(first, last: integer; var pmyrec: myrec): ITask;
var
  mylocalrec: myrec;
begin
  mylocalrec := pmyrec;

Поскольку mylocalrec и pmyrec являются записями (типами значений), их содержимое будет занимать два разных места в памяти. Вышеуказанное назначение скопирует содержаниеpmyrec в mylocalrec.

Это будет эквивалентно следующему:

mylocalrec.vet := pmyrec.vet;
mylocalrec.total := pmyrec.total;
mylocalrec.average := pmyrec.average;

Поскольку total и average также являются типами значений, их содержимое будет скопировано, и с этого момента любое изменение любого из этих целочисленных полей в mylocalrec не повлияет на исходный pmyrec. Вот почему ваш код не работает.

Почему назначение поля динамического массива vet из pmyrec в mylocalrec работает?

Поскольку динамические массивы являются ссылочными типами, при присвоении одной переменной другой копируется только значение ссылки (указатель), а не фактическое содержимое. Обе переменные vet будут указывать на тот же массив, который вы выделили перед запуском ваших задач.

Чтобы решить вышеуказанную проблему, вам необходимо передать какой-либо ссылочный тип вместо типа значения. Самое простое - объявить тип указателя записи и передать его.

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

  pmyrec = ^myrec;


function CreateTask(first, last: integer; rec: pmyrec): ITask;
var
  mylocalrec: pmyrec;
begin
  mylocalrec := rec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec^)
    end);
end;

...
Ptasks[indtask] := CreateTask(vfirst, vlast, @Recarray);

2. Как решить проблему многопоточности?

После решения исходной проблемы у вас возникнет проблема с потоками. Чтение и запись одной и той же области памяти из нескольких потоков небезопасно. Полученные вами результаты могут быть неверными. Хотя вы можете запускать свой код тысячу раз, и все значения могут быть правильными, рано или поздно вы столкнетесь с ситуацией, когда этого не произойдет.

Заполнение динамического массива в вашем случае безопасно, потому что каждый поток работает с отдельной частью массива, и массив не перераспределяется (его размер не изменяется) во время выполнения ваших задач. Эта часть вашего кода является потокобезопасной.

Расчет total и average не является потокобезопасным.

Вы должны синхронизировать такой код с основным потоком - в этом случае все чтение и запись будут выполняться из основного потока, и вы получите правильные результаты. Или вам нужно запустить такой код после того, как все задачи будут выполнены. Что больше подходит для конкретного случая.

procedure ProcA(const pin, pfin: integer; prec: pmyrec);
var
  vind: integer;
  total: integer;
begin
  total := 0;
  for vind := pin to pfin do
    begin
      prec.vet[vind] := vind * 10;
      total := total + prec.vet[vind];        // sum all array values
    end;

  TThread.Synchronize(nil,
    procedure
    begin
      prec.total := prec.total + total;
      prec.average := Trunc(prec.total / Length(prec.vet)); // calculates the average
    end);
end;

Однако синхронизация с основным потоком внутри задач вызовет взаимоблокировку с методом TTask.WaitForAll, который в вашем случае также запускается из основного потока. Чтобы решить эту проблему, вам также необходимо запустить весь метод Test из другого потока.

  TTask.Run(
    procedure
    begin
      Test;
    end);

И когда мы сложим все эти части вместе, полный код будет:

program ProjectTest;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  System.Classes,
  System.Threading;

type
  myrec = record
    vet: array of integer;
    total: integer;
    average: integer;
  end;

  pmyrec = ^myrec;

// (1) ===> here is the major procedure that populates the dyn. array and
// calculates other two fields  :  myrec.total  and myrec.avg

procedure ProcA(const pin, pfin: integer; prec: pmyrec);
var
  vind: integer;
  total: integer;
begin
  total := 0;
  for vind := pin to pfin do
    begin
      prec.vet[vind] := vind * 10;
      total := total + prec.vet[vind];        // sum all array values
    end;

  TThread.Synchronize(nil,
    procedure
    begin
      prec.total := prec.total + total;
      prec.average := Trunc(prec.total / Length(prec.vet)); // calculates the average
    end);
end;

// (2)  Here iTask is created and calls ProcA

function CreateTask(first, last: integer; rec: pmyrec): ITask;
var
  mylocalrec: pmyrec;
begin
  mylocalrec := rec;
  Result := TTask.Create(
    procedure
    begin
      ProcA(first, last, mylocalrec);
    end);
end;

procedure Test;
var
  Recarray: myrec;
  Ptasks: array of ITask;
  vind, indtask, vslice: integer;
  vfirst, vlast, vthreads, vsize: integer;
begin

  vthreads := 4;
  vsize := 16;

  SetLength(Ptasks, vthreads);
  SetLength(Recarray.vet, vsize);

  // Initialize the array , just to check after iTask execution
  for vind := low(Recarray.vet) to high(Recarray.vet) do
    Recarray.vet[vind] := -33;

  // initialize the sum and average field just to check after iTask execution
  Recarray.total := -1;
  Recarray.average := -2;

  // portion of array to scan for each iTask
  vslice := Length(Recarray.vet) div vthreads;

  for indtask := low(Ptasks) to high(Ptasks) do
    begin
      vfirst := indtask * vslice;
      vlast := (indtask + 1) * vslice - 1;

      if (Length(Recarray.vet) mod vthreads <> 0) and (indtask = high(Ptasks)) then vlast := high(Recarray.vet);

      Ptasks[indtask] := CreateTask(vfirst, vlast, @Recarray);
    end;

  // Starting all Tasks
  for indtask := low(Ptasks) to high(Ptasks) do
    Ptasks[indtask].Start;

  // Waits for all Tasks been concluded
  TTask.WaitForAll(Ptasks);

  // (3) Here it is listed the array contents and it is ok
  for vind := low(Recarray.vet) to high(Recarray.vet) do
      Writeln(' Array position  : ' + Format('%.3d', [vind]) + '   content  : ' + Recarray.vet[vind].tostring);

  Writeln(' =========================================================');

// (4) Here is is listed fields recarray.total and recarray.avg and they were not
// processed inside the iTask .    I expected to see the computed values for those fields

  Writeln(' Array sum   : ' + Format('%.0d', [Recarray.total]) + '    Array average   : ' + Format('%5.2n', [Recarray.average * 1.0]));

end;

begin
  TTask.Run(
    procedure
    begin
      Test;
    end);
end.

Примечание для @JRG. Несмотря на то, что этот ответ демонстрирует, как это сделать правильно (+1), ваша стратегия разделения вычислений на несколько потоков имеет свою цену. Это усложняет код и примерно в 5000 раз медленнее, чем прямое вычисление в одном потоке.

LU RD 14.07.2018 12:02

@Dalija, помимо помощи в решении этой проблемы, я думаю, что ваш главный вклад заключался в том, чтобы поделиться такими ценными и точными концепциями, представленными в ясной, точной и поучительной форме, большое спасибо! Я все понял. Использование указателя для myrec было ключевой частью, которую я долго искал сам! Я буду рассчитывать на вас в следующих моих вопросах. Большое спасибо !

JRG 14.07.2018 18:50

@LU RD, полностью с вами согласен, спасибо за оповещение! Я должен позаботиться о том, чтобы избежать сложности кода и рисков. Что касается скорости, я тщательно ее протестирую, поскольку моей основной целью при разделении обработки массива на задачи было повышение скорости, а не наоборот! С концептуальной точки зрения ожидается, что код, разделенный на потоки, будет быстрее. Почему вы сказали, что он примерно на 5000 медленнее? В реальном приложении мой динамический массив будет содержать от 20 000 до 40 000 записей. Я ценю ваш комментарий по этому поводу.

JRG 14.07.2018 18:58

@JRG, преждевременная оптимизация может привести к ненужному сложному коду. Оптимизируйте только в том случае, если в вашем реальном приложении есть проблемы с производительностью, а затем используйте инструменты анализа приборов для поиска узких мест.

LU RD 14.07.2018 20:50

@JRG, Мой тест вызвал процедуру Test (без накладных расходов на распечатку) 100000 раз с разделением на потоки и без него, что дало такую ​​большую разницу в скорости. Если бы я вызвал только один раз, но с размером vet 1,6 миллиона элементов, одиночный поток все равно был в 6 раз быстрее.

LU RD 14.07.2018 21:24

@LU RD, я внимательно рассмотрю эту проблему с производительностью. В моем приложении есть динамический массив записей, совершенно отличный от кода, который я опубликовал. У меня действительно была основная проблема параметров «значения по сравнению с эталоном в iTask», и я мог решить ее с помощью предложенных здесь ответов, НО в отношении производительности, когда я разделил обработку массива на 4 потока, я мог снизить скорость в среднем на 60%, что я считаю хорошей оценкой. К сожалению, сейчас я столкнулся с новой проблемой: утечки памяти. Объем памяти увеличивается, и когда все 4 iTasks завершают работу, она не возвращается к нормальному уровню. Мне скоро снова понадобится твоя помощь.

JRG 15.07.2018 07:13

Другие вопросы по теме