TASKS ===== -------------------- *Consider the following outline of an Ada program declare -- some declarative regions executed by task parent task type T_type; type Prt_T_type is access T_type; A1 : T_Type; -- creation of A1 A2 : T_Type; -- creation of A2 task body T_Type is .. (not shown here) begin -- activation of A1, A2 Block_in_master: (the following is somewhere later in master, could be in procedure) declare B : T_Type; -- Creation of B C : Prt_T_Type := new T_Type; -- creation, activation of C.All D : Prt_T_Type; begin -- activation of B D := new T_Type; - creation, activation of D.all end Block_in_master; (end somewhere later in master) end; In what order are the tasks created and activated in the above code? (I am expecting you to outline 8 phases). 1. Task A1 is created 2. Task A2 is created 3. Tasks A1 and A2 are activated concurrently and the parent waits for them to finish their activation. 4. Assuming a successful activation A1, A2 and Parent proceed concurrently. 5. Parent creates task B 6. Parent creates and activates task C, and waits for it to finish activation 7. Parent activates task B and waits for it to finish activation 8. Parent creates and activates task D, and waits for it to finish activation. -------------------- * Why can you not allow the master task to terminate before the child? (Verify with tutor in lab). Because the child may be accessing a shared variable declared in the master task. -------------------- * Write an Ada program that takes as input a positive integer of value N and computes explicitly the sum of all integers from 1 to N, i.e. 1+2+3+4+5+6...N. The program should verify that it has computed the correct result by comparing the computed value with the expected value of N*(N+1)/2. with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Sum_ints is Value, Total1, Total2 : Integer; begin Put("Input value of N"); New_Line; Get(Value); Put("Value of N "); Put(Value); New_Line; Total1 := 0; for I in 1..Value loop Total1 := Total1 + I; end loop; Put_Line("Computed Sum " & total1'Img ); Total2 := Value * (Value+1)/2; Put_Line("Expected Sum " & total2'Img ); end Sum_Ints; **Now modify your code to create up to 20 child tasks, with the exact number of tasks created determined at run time by an input parameter. Have each child task compute a distinct sub-range of the explicit sum, e.g. if N=6 and there are two tasks then task 1 computes 1+2+3, while task 2 computes 4+5+6. Have the parent task sum the different task sums and print out the final result. Be sure that the parent compares the value computed with that obtained from the formula given above. Your program should only use access to shared variables to control synchronization. (That is have tasks busy wait until the value of a shared variable is changed. Be sure to include a delay 0.0 to permit task swapping. If this is unclear you need to revise the last part of lab2!) This conversion can be tackled in stages. First, modify the sequential code to use two tasks: the master task which gets the value of N from the user and passes it on to the child process. The child process computes and displayes the sum. Second, introduce some synchronization by have the parent print out the value computed by the child. When this is working, modify the child to take a range to sum over. (That is, instead of passing only N, pass 1 and N as lower and upper bounds) Finally, create multiple tasks, each taking a different range. with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Sum_Ints is Max_Children : constant Positive := 20; type Job is record Total : Natural := 0; From,To : Positive; Finished : Boolean := False; end record; Jobs : array( 1..Max_Children ) of Job; task type Adder ( Index : Natural ); task body Adder is begin for I in Jobs(Index).From..Jobs(Index).To loop Jobs(Index).Total := Jobs(Index).Total + I; end loop; Jobs(Index).Finished := True; end Adder; type Adder_Ptr is access all Adder; Children : array( 1..Max_Children ) of Adder_Ptr := ( others => null ); Total : Natural; From, To, Expected, Step_Per_Child: Positive; Num_To_Sum : Positive; subtype Task_Num is Natural range 1..Max_Children; Num_Child_Tasks : Task_Num; begin Put("N? "); Get( Num_To_Sum ); Put("Tasks? "); Get( Num_Child_Tasks ); -- Split the workload. Step_Per_Child := Num_To_Sum / Num_Child_Tasks; From := 1; To := Step_Per_Child; for T in 1..Num_Child_Tasks - 1 loop Jobs(T).From := From; Jobs(T).To := To; From := From + Step_Per_Child; To := To + Step_Per_Child; end loop; -- clean up overspill Jobs( Num_Child_Tasks ).From := From; Jobs( Num_Child_Tasks ).To := Num_To_Sum; for T in 1..Num_Child_Tasks loop Children(T) := new Adder( T ); end loop; Total := 0; for T in 1..Num_Child_Tasks loop while not Jobs(T).Finished loop delay 0.0; end loop; Total := Total + Jobs(T).Total; end loop; Expected := (Num_To_Sum * ( Num_To_Sum + 1 ) ) / 2; Put_Line("Total " & Total'Img ); Put_Line("Expected " & Expected'Img ); end Sum_Ints; ---------------- * The following program attempts to use rendezvous to synchronize all child tasks with the parent. Specifically the intention is that no child task will begin to execute the "for i in 1..10 loop" until all child tasks have reached that point. Similarly it is intended that no child task terminates until all child tasks have finished the aforementioned loop. Unfortunately the synchronization is not working correctly. Run the program and verify that it is indeed true. Then fix it. The problem is that we must have two phases to the synchronization. The master must first pairwise sync with all the child tasks. Once this is complete we know that all the child tasks have reach this point. We can release them by a second series of pairwise syncs. Of course this is not an efficient barrier - but we don't worry about that here! with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Calendar; procedure Synchronize is task type Task_Type is entry Receive_Task_Id (Task_Id : in Positive); entry Start; entry Stop; end Task_Type; task body Task_Type is Year : Ada.Calendar.Year_Number; Month : Ada.Calendar.Month_Number; Day : Ada.Calendar.Day_Number; Seconds : Ada.Calendar.Day_Duration; Wait : Float; Id : Positive; begin accept Receive_Task_Id (Task_Id : in Positive) do Id := Task_Id; end Receive_Task_Id; Put("Hello From Task "); Put( Id); New_Line; Wait := Float(Id)*1.0; -- Start of the first barrier. accept Start do Put_Line("Task " & Id'Img & " entering barrier 0"); end Start; accept Start do Put_Line("Task " & Id'Img & " leaving barrier 0"); end Start; -- End of first barrier for I in 1..5 loop Ada.Calendar.Split(Ada.Calendar.Clock, Year, Month, Day, Seconds); Put("Task "); Put(Id); Put(" " & Seconds'img ); New_Line; delay Duration(Wait); end loop; -- Start of the second barrier. accept Stop do Put_Line("Task " & Id'Img & " entering barrier 1"); end Stop; accept Stop do Put_Line("Task " & Id'Img & " leaving barrier 1"); end Stop; -- End of second barrier end Task_Type; Task_Array : array (Integer range 1..2) of Task_Type; begin for I in Task_Array'Range loop Task_Array(I).Receive_Task_Id (I); end loop; for I in Task_Array'Range loop Task_Array(I).Start; end loop; for I in Task_Array'Range loop Task_Array(I).Start; end loop; for I in Task_Array'Range loop Task_Array(I).Stop; end loop; for I in Task_Array'Range loop Task_Array(I).Stop; end loop; end; -------------------- *The following code is designed to create a number of child tasks and then initiate a ring of synchronizations, i.e. synchronizations that go parent-child0, child0-child1, child1-child2, child2-child3, child3-child4, child4-parent. At each synchronization point the value of a counter is incremented before being passed from task to task, thus in the following the parent should receive back a value for the counter of 7. Unfortunately the code is not complete. Finish it! Bit trickier. Need to understand ordering of events. Need to change definition of start and stop to pass parameters. Note that the actual value of the counter that the master will get is dependent on how you read the above question. I have incremented the counter at each synchronisation point that has been commented as such. The entry receive_task_id is also a synchronisation point, so if you wanted, you could modify the code to add one each time receive_task_id is called (Note that this could get complicated, since you will need to pass additional data around). Alternatively, the first and last points, while synchronising with the master task, do not synchronisze with other tasks in the ring. If you exclude these points, the final count should be 5. with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Ring is type Ring_Range is mod 5; task type Task_Type is entry Receive_Task_Id (Task_Id : in Ring_Range); entry Start; entry Ring (Count_In : in Natural); entry Stop ( Count_Out : out Natural ); end Task_Type; Task_Array : array (Ring_Range) of Task_Type; task body Task_Type is Counter : Natural := 0; To_Whom : Ring_Range; Id : Ring_Range; begin accept Receive_Task_Id (Task_Id : in Ring_Range) do Id := Task_Id; end Receive_Task_Id; Put_Line("Hello From Task " & Id'Img ); delay 1.0; -- first task synchronize with parent if Id = Task_Array'First then Counter := Counter + 1; accept Start; end if; -- synchronize with neighbour To_Whom := Id + 1; if Id = Task_Array'First then Task_Array (To_Whom).Ring(Counter); accept Ring (Count_In : Natural) do Counter := Count_In + 1; end Ring; else accept Ring (Count_In : Natural) do Counter := Count_In + 1; end Ring; Task_Array (To_Whom).Ring(Counter); end if; Put_Line ("Task " & Id'Img & " counts " & Counter'Img ); -- first task synchronize with parent if Id = Task_Array'First then accept Stop( Count_Out : out Natural ) do Count_Out := Counter + 1; end Stop; end if; end Task_Type; Global_Counter : Natural := 0; begin -- start children for I in Task_Array'Range loop Task_Array(I).Receive_Task_Id (I); end loop; -- tell first child to start Task_Array (Task_Array'First).Start; Task_Array (Task_Array'First).Stop( Global_Counter ); Put_Line("Master received value " & Global_Counter'Img ); end Ring; Now re-write the multi-task summation program that you developed above to use rendezvous constructs to communicate between the parent and child tasks. with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; procedure Rendezvous_Sum is Max_Children : constant Positive := 20; task type Adder ( Index : Natural ) is entry Start( F, T : in Positive ); entry Finish ( Res : out Natural ); end Adder; task body Adder is From, To : Positive; Total : Natural; begin accept Start( F, T : in Positive ) do From := F; To := T; end Start; Total := 0; for I in From..To loop Total := Total + I; end loop; accept Finish ( Res : out Natural ) do Res := Total; end Finish; end Adder; type Adder_Ptr is access all Adder; Children : array( 1..Max_Children ) of Adder_Ptr := ( others => null ); Total,Bit : Natural; From, To, Expected, Step_Per_Child: Positive; Num_To_RSum : Positive; subtype Task_Num is Natural range 1..Max_Children; Num_Child_Tasks : Task_Num; begin Put("N? "); Get( Num_To_RSum ); Put("Tasks? "); Get( Num_Child_Tasks ); for T in 1..Num_Child_Tasks loop Children(T) := new Adder( T ); end loop; -- Split the workload. Step_Per_Child := Num_To_RSum / Num_Child_Tasks; From := 1; To := Step_Per_Child; for T in 1..Num_Child_Tasks - 1 loop Children(T).Start( From, To ); From := From + Step_Per_Child; To := To + Step_Per_Child; end loop; -- clean up overspill Children( Num_Child_Tasks ).Start( From, Num_To_Rsum ); Total := 0; for T in 1..Num_Child_Tasks loop Children(T).Finish( Bit ); Total := Total + Bit; end loop; Expected := (Num_To_RSum * ( Num_To_RSum + 1 ) ) / 2; Put_Line("Total " & Total'Img ); Put_Line("Expected " & Expected'Img ); end Rendezvous_Sum;