Задачи (паралелно програмиране)
Е, стигнахме до задачите. Исках да се побера в една част, но не се получи. И така задачи. Ще се опитам да го направя по-ясно.
В нашето ежедневие много събития се случват едновременно (паралелно). Те почти винаги не са синхронизирани във времето, но на определен етап се случват едновременно. Например, влакове пътуват по една и съща линия по едно и също време, самолети излитат и кацат по едно и също време, заявки от много потребители към сайта също се случват едновременно, касиерите в магазин могат едновременно да пробият един и същ продукт => едновременен достъп до базата данни, хората живеят един до друг (паралелно) и т.н. За управление на паралелни процеси е удобно да се пишат програми, които биха позволили разпределяне на натоварването между различен хардуер и правилно пренасяне на резултата в една система. Например, ако два, три или десет самолета кацат едновременно, тогава програмата трябва едновременно да обработва информацията за тези самолети и да им изпраща правилните данни за кацане. Можете, разбира се, да разпределите процесорното време между тези десет самолета и да обработвате информация за всеки от тях на части на един процесор, но. ако няма десет самолета, а сто или хиляда, тогава това вече ще е проблем.
задача My_First_Task; --анонимна задача тип задача My_Second_Task; --task тип задача тип My_Third_Task(n : Integer); --задачата ще приема параметри (дискриминант)
задача My_First_Task; тяло на задачата My_First_Task е начало цикъл --безкраен цикъл Put_Line("Hello, World!"); крайна линия; край на моята_първа_задача; . тип задача My_Third_Task(n : Integer); тялото на задачата My_Third_Task е cnt : Integer := n; начало за i в 1..cnt цикъл Put(Item => i, Width => 3);крайна линия; end My_Third_Task;
Ако погледнете монитора на ресурсите след стартиране на тази програма, можете да видите, че тя се изпълнява в три нишки: една нишка е примерната процедура и две нишки са задачи:
И трите нишки работят независимо една от друга. Е, резултатът от изпълнението изглежда така:
Тоест, въпреки факта, че основната процедура чака натискане на бутона, задачите се изпълняват независимо от нея и една от друга.
По този начин можете да изпълнявате много задачи. Ако обаче имате един процесор и създавате две или повече задачи, тогава това просто ще емулира многопоточност. Няма чудеса 🙂
В примера използвахме оператора за забавяне. Има и друг начин да го използвате:отлагане до :
Типът Time е описан в пакетите Ada.Calendar и Ada.Real_Time (когато разработвате системи в реално време, е по-добре да използвате последния пакет).
Завзаимодействието на задачите една с друга в Ada е създаден механизъмРандеву (фр. rendez-vous - среща, среща). Помислете за аналогия: да кажем, че имаме двама души - момче и момиче. Те се съгласиха да се срещнат (среща) и човекът с цветя (информация за обмен) дойде на среща малко по-рано от момичето (първата задача се опитва да обмени информация с втората задача, но втората задача не е готова да приеме информация от първата). Момчето (първа задача) няма друг избор, освен да изчака пристигането на момичето (да изчака втората задача да е готова за обмен на информация. Още веднъж, задачите са независими една от друга и не могат да бъдат извикани като подпрограма по всяко време). И едва когато момичето пристигне (т.е. втората задача ще бъде на този етап на изпълнение (тези инструкции ще бъдат изпълнени), когато тя може да обработи заявката на първата задача), момчетоще й даде цветя (ще има среща и обмен на информация).
За да работи механизмът за среща, задача (в този случай втората задача) създава поне единвход, където тя е готова да чака друга задача да я извика (да се върнем към нашата аналогия - за да вземе цветя, едно момиче трябва да има поне една ръка - един вход 🙂 ).
Записът се декларира подобно на процедура (включително режими на достъп in, out, in out), но вместо думатаprocedure се използва думатаentry:
--Task type task type My_First_Task е --Task ще има една входна точка Input entry Input(str : Unbounded_String; num : Integer); край на моята_първа_задача;
В тялото на задачата операторът за приеманеaccept се използва за описание на инструкции за въвеждане. Това означава, че задачата ще бъде спряна и ще чака заявка.accept определя условията за възникване на срещата. Помислете за пример:
с Ada.Strings.Unbounded; използвайте Ada.Strings.Unbounded; с Ada.Text_IO; използвайте Ada.Text_IO; --с MyTask; procedure main is --Task type task type My_First_Task is --Task ще има само една входна точка Input entry Input(str : Unbounded_String; num : Integer); край на моята_първа_задача; --task body body на задача My_First_Task е s: Unbounded_String; n : Цяло число begin --accept означава, че в този момент задачата ще бъде спряна -- докато не получи заявка към входната точка Input accept Input(str : Unbounded_String; num : Integer) do --Задачата получава две променливи като вход. Те трябва да бъдат копирани в локално --Това е мястото, където се случва срещата s := str; n := Брой; endInput; for i в 1..n цикъл --Изведе съобщението, предадено на входа на задачата и стъпката за изпълнение на задачата Put_Line(To_String(s) &Цяло число'Image(i)); забавяне 0,5; крайна линия; край на моята_първа_задача; --Създайте две задачи mft_1 : My_First_Task; mft_2 : Моята_първа_задача; begin --Тук и двете задачи ще започнат своето изпълнение, но ще бъдат спрени в точките за достъп --За да стартирате задачи, трябва да им изпратите заявка за въвеждане (тя е единствената в задачите): mft_2.Input(To_Unbounded_String("Task 2. Stage"), 5); mft_1.Input(To_Unbounded_String("Задача 1. Етап"), 10); крайна главна;
Е, или ако е по-лесно за някой с горната аналогия за момче и момиче:
с Ada.Strings.Unbounded; използвайте Ada.Strings.Unbounded; с Ada.Strings.Unbounded.Text_IO; използвайте Ada.Strings.Unbounded.Text_IO; процедура main is --task "Girl" task type girl is --input - get flowers entry GetFlowers(str : Unbounded_String); крайно момиче; тялото на задачата момиче е цветя : Unbounded_String := To_Unbounded_String("няма цветя"); начало --след отпечатване на този ред задачата ще бъде спряна Put_Line("Имам " & цветя); --Rendezvous - вземете цветя приемете GetFlowers(str : Unbounded_String) направете цветя := str; край на GetFlowers; Put_Line("Сега имам " & цветя); крайно момиче; gl : момиче; --създаване на задача --Задача "Момче" task boy; тяло на задачата момче е начало --Подарете цветя (gl запис на задача) gl.GetFlowers(To_Unbounded_String("daisies")); забавяне1.0; крайно момче; начало нула; крайна главна;
Една задача може да има множество входове. Тогава, за да се избегне блокиране във всеки оператор за приемане в Ada, се използва изразът за изборselect.
Да кажем, че имаме задача с множество входове:
задача My_Task е запис Input_One(Параметри); --Параметрите могат или не могат да бъдат запис Input_Two(Параметри); запис Input_Three(Опции); . запис Input_X(Опции); край на моята_задача;
Тогава за възможността за избор (изчакване) на повече от едно рандеву трябва да се използва следната конструкция:
тяло на задачата My_Task е начало цикъл изберете приемане Input_One(Параметри) do. край на Input_One; или приемете Input_Two(Parameters) do . end Input_Two; или приемете Input_Three(Parameters) do . end Input_Three; или. или приемете Input_X(Parameters) do . край Input_X; или прекрати; --край на задача край изберете; крайна линия; край на моята_задача;
В този блок входовете се проверяват циклично за заявка към един от тях. Тук можете да посочите условие за изпълнение на задачата, например, когато е получена заявка за определен вход или като цяло, ако не е получена заявка:
тяло на задачата My_Task е начало цикъл изберете приемане Input_One(Параметри) do. край на Input_One; или. или приемете Input_X(Parameters) do . край Input_X; или прекрати; --край на задача край изберете; крайна линия; край на моята_задача;
Но най-често ми се струва, че в процеса на чакане на среща задачата трябва да извърши някои действия (някаква работа). За да направите това, можете да създадете секция else в конструкцията select:
тяло на задачата My_Task е начало цикъл изберете приемане Input_One(Параметри) do. край на Input_One; или. или приемете Input_X(Parameters) do . край Input_X; иначе . --някои действия завършват с избор; крайна линия; край на моята_задача;
Освен това в конструкцията select можете да зададете интервала от време, през който ще се правят опити за установяване на среща:
тяло на задачата My_Task е начало цикъл изберете приемане Input_One(Параметри) do. край на Input_One; или. или приемете Input_X(Parameters) do . край Input_X; или забавяне 3.0; --Опити за установяване на среща в рамките на 3 секунди. --Нещо, което трябва да направите, ако срещата не се е състояла, изберете край; крайна линия; край на моята_задача;
В рамките на един блокselect може да има само един от следните изрази:terminate,delay илиelse. А може и да няма такъв, тезиинструкциите не са задължителни.
Можете да организирате допълнителна проверка на някои условия, преди да приемете среща:
изберете кога => приемам Input_One(Параметри) do . край на Input_One; или. избор на край;
Операторът selcet може да се използва не само в приемащата задача, но и в извикващата. Например, ако получаващата задача не е готова да установи среща, тогава извикващата задача ще извърши някои действия в този момент:
изберете My_Task.Input_One(. ); else Put("Не може да се зададе рандеву"); избор на край;
Това означава, че използването на select е еднакво както за получаващата задача, така и за извикващата.