diff --git a/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/1.pas b/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/1.pas index 3838db0bb..aba2751d6 100644 --- a/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/1.pas +++ b/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/1.pas @@ -16,12 +16,15 @@ begin // var A := NamedQ('A'); - var B := NamedQ('B'); - var C := NamedQ('C'); + var B := NamedQ('B').ThenWaitMarker; + var C := NamedQ('C').ThenWaitMarker; var D := NamedQ('D'); var E := NamedQ('E'); var F := NamedQ('F'); + // Можно попробовать расписать как "A + B*C + D*E*F", но это будет не то же самое: + // К примеру D будет ожидать окончание выполнения C перед тем как начать выполняться + // (когда все очереди выполняются за ровно одну секунду - это не заметно, но в реальной ситуации будет существенно) Context.Default.SyncInvoke( A + (B+D) * diff --git a/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/2.pas b/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/2.pas index b498002f8..08d39ad5d 100644 --- a/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/2.pas +++ b/InstallerSamples/OpenGL и OpenCL/OpenCLABC/Wait очереди/2.pas @@ -13,19 +13,27 @@ begin var A1 := NamedQ('A1'); var A2 := NamedQ('A2'); - var B1 := NamedQ('B1'); + var B1 := NamedQ('B1').ThenWaitMarker; + var Bw := WaitFor(B1); var C1 := NamedQ('C1'); var C2 := NamedQ('C2'); - var D2 := NamedQ('D2'); + var D2 := NamedQ('D2').ThenWaitMarker; + var Dw := WaitFor(D2); var E1 := NamedQ('E1'); var E2 := NamedQ('E2'); + // Дерево выполнения из данной программы может решать задачу такого типа: + // > Два парралельных процесса выполняются последовательно и иногда взаимодействуют + // Его можно расписать без Wait, к примеру как "A1*A2 + B1 + C1*C2 + D2 + E1*E2" + // И результат будет тот же, но такое дерево уже не похоже по форме на изначальную задачу + // Если между деревом и задачей сложно провести параллели - программу будет сложно улучшать + // (то есть будет сложно понять что делает что в очереди) Context.Default.SyncInvoke( - ( A1 + B1 + C1 + WaitFor(D2) + E1 ) * - ( A2 + WaitFor(B1) + C2 + D2 + E2 ) + ( A1 + B1 + C1 + Dw + E1 ) * + ( A2 + Bw + C2 + D2 + E2 ) ); end. \ No newline at end of file diff --git a/InstallerSamples/OpenGL и OpenCL/Справка OpenCLABC.html b/InstallerSamples/OpenGL и OpenCL/Справка OpenCLABC.html index 38ca71983..cfa49fa39 100644 --- a/InstallerSamples/OpenGL и OpenCL/Справка OpenCLABC.html +++ b/InstallerSamples/OpenGL и OpenCL/Справка OpenCLABC.html @@ -1766,67 +1766,127 @@ q.AddQueue(q);
Есть всего 3 группы подпрограмм, создающих очереди для ожидания других очередей:
+Большинство деревьев выполнения очередей можно реализовать используя только сложение и умножение очередей. Но есть несколько проблем:
+Большинство но не все. Пример дерева которое нельзя реализовать через сложение и умножение можно найти в файле:
+C:\PABCWork.NET\Samples\OpenGL и OpenCL\OpenCLABC\Wait очереди\1.pas
Даже если дерево реализуется сложением+умножением - такая реализация может выглядеть запутано и не похоже на график дерева выполнения:
+C:\PABCWork.NET\Samples\OpenGL и OpenCL\OpenCLABC\Wait очереди\2.pas
Очереди, выполняемые на разных контекстах не могут эффективно взаимодействовать.
+Эти проблемы решают Wait-очереди:
uses OpenCLABC;
+
+begin
+ var M := new WaitMarker;
+
+ var t := Context.Default.BeginInvoke(
+ HPQ(()->Writeln('Начало .BeginInvoke')) +
+ WaitFor(M) +
+ HPQ(()->Writeln('Конец .BeginInvoke'))
+ );
+
+ Sleep(2000);
+ Writeln('Посылаем сигнал маркера M');
+ M.SendSignal;
+
+ t.Wait;
+end.
+
+WaitFor(M) создаёт очень простую Wait-очередь.
Внутри вызова .BeginInvoke (до его завершения) эта очередь
+входит в режим ожидания сигналов от маркера M.
+В этом режиме она Не тратит время процессора, но готова возобновить выполнение в любой момент.
Далее, вызов M.SendSignal посылает сигнал всем Wait очередям,
+которые в данный момент находятся в режиме ожидания маркера M.
WaitMarker, так же, является своеобразной очередью, то есть
+его можно складывать с другими очередями и передавать в .BeginInvoke
При выполнении в качестве очереди маркер вызывает свой метод .SendSignal и сразу возвращает object(nil).
+То есть как очередь он равноценен HPQ(M.SendSignal), но немного более эффективен.
Но стоит так же сказать, прямой вызов M.SendSignal всё равно всегда эффективнее чем Context.Default.SyncInvoke(M).
+Используйте выполнение маркеров внутри .BeginInvoke только если вам надо активировать его сразу после других очередей:
var M := new WaitMarker;
+
+ Context.Default.SyncInvoke(
+ HFQ(()->5) + M
+ );
+
+Но в этом же коде видно ещё одну проблему - сложение очередей возвращает последний результат,
+то есть результат маркера (который object(nil)).
Если нужно иметь сразу и маркер и возвращаемое значение предыдущей очереди,
+можно создать псевдо-маркер методом .ThenWaitMarker:
uses OpenCLABC;
+
+begin
+ var Q := HFQ(()->5).ThenWaitMarker;
+
+ var t := Context.Default.BeginInvoke( WaitFor(Q) );
+ Context.Default.SyncInvoke( Q ).Println;
+
+ t.Wait;
+end.
+
+Псевдо-маркеры разлагаются в настоящие маркеры при передаче в Wait очереди,
+но при этом сохраняют результат оригинальной очереди при выполнении в .BeginInvoke.
Точнее Q в последнем коде сначала выполнит HFQ, затем пошлёт сигнал в
+Wait очередь WaitFor(Q) и в конце вернёт то, что вернула HFQ.
Есть всего 3 группы подпрограмм, создающих Wait очереди:
Глобальные, WaitFor*:
-Ничего не делают сами, но блокируют выполнение пока указанные очереди не выполнятся.
Особые методы .Add* - .AddWait*:
Как и .AddQueue и .AddProc, .AddWait(...) это всего лишь аналог
.AddQueue(WaitFor(...)), существующий только ради простоты кода.
Методы очередей, .ThenWaitFor*:
-Так же не имеет незаменимых применений, но ещё полезнее.
-Q.ThenWaitFor(...) работает как Q + WaitFor(...), за исключением того, что возвращает результат Q.
-То есть, правильнее будет:
var Qs := Q.Multiusable;
-Result := Qs() + WaitFor(...) + Qs();
-
-Но это уже даже в одну строчку не напишешь.
+Как и.ThenWaitMarker, сначала выполняет исходную очередь, потом ожидает и в конце возвращает результат исходной очереди.
В каждой из групп Wait очередей - * можно заменить:
В каждой из групп Wait очередей * можно заменить:
All: Ожидание всех указанных очередей.Any: Ожидание любой из указанных очередей (какая раньше выполнится).То есть, к примеру, WaitForAny создаёт очередь, которая сама ничего не выполняет, но ожидает выполнения любой из указанных очередей.
То есть, к примеру, WaitForAny создаёт Wait очередь, которая сама ничего не выполняет, но ожидает сигнала от любого из указанных маркеров.
Wait очереди делают реализацию некоторых сложных деревьев выполнения очередей возможными.
-Примеры можно найти в папке PABCWork.NET\Samples\OpenCLABC\Wait очереди.
Но они наиболее полезны благодаря одному особенному свойству:
-Wait очереди работают даже между вызовами Context.BeginInvoke, в отличии от всего остального в OpenCLABC.
Wait очереди между вызовами Context.BeginInvokeWait очереди работают даже между вызовами Context.BeginInvoke, в отличии от всего остального в OpenCLABC.
Это не всегда безопастно:
Context.Default.BeginInvoke(Q1);
Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
-Проблема этого кода в том, что Q1 может закончить выполняться ещё до того как WaitFor(Q1) начнёт ожидать.
Чтоб такое не происходило - надо всегда запускать ожидающую очередь раньше ожидаемой:
+Проблема этого кода в том, что Q1 может послать сигнал ещё до того как WaitFor(Q1) начнёт ждать.
Чтоб такое не происходило - надо всегда запускать Wait очередь раньше маркера:
Context.Default.BeginInvoke(WaitFor(Q1) + Q2);
Context.Default.BeginInvoke(Q1);
-Но, как всегда, лучше, по возможности, объединять вызовы Context.BeginInvoke:
Но, как всегда, лучше объединять вызовы Context.BeginInvoke:
Context.Default.BeginInvoke(
( Q1 ) *
( WaitFor(Q1) + Q2 )
);
-Все ожидающие очереди начинают ожидать в самом начале вызова Context.BeginInvoke, перед тем как очередь начнёт выполнятся.
-Поэтому если ожидающая и ожидаемая очереди находятся в общем Context.BeginInvoke - о их порядке можно не волноваться.
Все Wait очереди начинают ждать в самом начале вызова Context.BeginInvoke, перед началом выполнения очереди.
+Поэтому если Wait очередь и маркер находятся в общем Context.BeginInvoke - маркер никогда не активируется.
Когда начинает выполняться Context.BeginInvoke - для каждой ожидающей очереди в соответствующей
-ожидаемой очереди создаётся новый счётчик для того, сколько раз ожидаемая очередь была выполнена.
Когда начинает выполняться Context.BeginInvoke - для каждой Wait очереди в соответствующем маркере
+создаётся новый счётчик для того, сколько раз маркер был активирован.
Поэтому можно делать так:
uses OpenCLABC;
begin
var Q1 := HPQ(()->
begin
- Sleep(1000);
+ Sleep(10);
lock output do Writeln('Выполнилась Q1');
- end);
+ end).ThenWaitMarker;
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
@@ -1846,9 +1906,9 @@ end.
begin
var Q1 := HPQ(()->
begin
- Sleep(1000);
+ Sleep(10);
lock output do Writeln('Выполнилась Q1');
- end);
+ end).ThenWaitMarker;
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
@@ -1861,13 +1921,13 @@ begin
end.
-Каждое окончание выполнения Q1 добавляет 1 в счётчик внутри Q1.
-Каждое окончание ожидания WaitFor(Q1) отнимание 1 от того же счётчика.
Каждое окончание выполнения Q1 добавляет 1 в счётчик внутри маркера.
+Каждое окончание ожидания WaitFor(Q1) отнимает 1 от того же счётчика.
Будьте осторожны, лишняя Wait очередь вызовет зависание:
uses OpenCLABC;
begin
- var Q1 := HFQ(()->0);
+ var Q1 := HFQ(()->0).ThenWaitMarker;
var t1 := Context.Default.BeginInvoke(
WaitFor(Q1) +
@@ -1879,7 +1939,7 @@ begin
end.
Cчётчик создаётся для каждой пары [ожидаемой очереди] и [вызова Context.BeginInvoke с ожидающей очередью].
+
Cчётчик создаётся для каждой пары [маркера] и [вызова Context.BeginInvoke с Wait очередью].
То есть это тоже сработает:
uses OpenCLABC;
@@ -1888,7 +1948,7 @@ begin
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
- end);
+ end).ThenWaitMarker;
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
@@ -1904,14 +1964,14 @@ begin
( WaitFor(Q1)+Q4 ) *
( WaitFor(Q1)+Q5 )
);
- // Каждый вызов Q1 тут - активирует по 1 WaitFor(Q1) в каждом CLTask
+ // Каждый вызов Q1 тут - активирует по одному WaitFor(Q1) в каждом .BeginInvoke
Context.Default.SyncInvoke(Q1+Q1);
t1.Wait;
t2.Wait;
end.
-Но каждый Context.BeginInvoke для ожидаемой очереди - добавляет в уже существующий счётчик:
Но каждая активация маркера добавляет 1 в уже существующий счётчик:
uses OpenCLABC;
begin
@@ -1919,21 +1979,20 @@ begin
begin
Sleep(1000);
lock output do Writeln('Выполнилась Q1');
- end);
+ end).ThenWaitMarker;
var Q2 := HPQ(()->lock output do Writeln('Выполнилась Q2'));
var Q3 := HPQ(()->lock output do Writeln('Выполнилась Q3'));
var Q4 := HPQ(()->lock output do Writeln('Выполнилась Q4'));
- var t1 := Context.Default.BeginInvoke(
+ var t := Context.Default.BeginInvoke(
( WaitFor(Q1)+Q3 ) *
( WaitFor(Q1)+Q4 )
);
- var t2 := Context.Default.BeginInvoke(WaitFor(Q1)+Q2+Q1);
+ Q1.SendSignal;
Context.Default.SyncInvoke(Q1);
- t1.Wait;
- t2.Wait;
+ t.Wait;
end.