ADA 95教程 高级特性 条件会合

不耐烦的编程

在你的生活中,很多时候你想要做一些事情,但在尝试了一段时间后,你发现它不可能完成,于是你改变了你的想法,去做其他的事情。这种能力也必须在计算机程序中可用,因为计算机程序经常被用来模拟现实生活中的某些东西。本章将给出一些没有耐心的程序的例子,这些程序拒绝永远等待某项工作完成。

 

基本程序大纲

Example program ------> e_c28_p1.ada

                                    -- Chapter 28 - Program 1
with Ada.Text_IO;
use Ada.Text_IO;

procedure Meals1 is

   HOURS : constant := 1;
   type PERSON is (BILL, JOHN);

   package Enum_IO is new Ada.Text_IO.Enumeration_IO(PERSON);
   use Enum_IO;

   task Bills_Day;

   task Johns_Day;

   task Restaurant is
      entry Eat_A_Meal(Customer : PERSON);
   end Restaurant;

   task Burger_Boy is
      entry Eat_A_Meal(Customer : PERSON);
   end Burger_Boy;

   task body Bills_Day is
      My_Name : PERSON := BILL;
   begin
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Bills_Day;

   task body Johns_Day is
      My_Name : PERSON := JOHN;
   begin
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 4.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Johns_Day;

   task body Restaurant is
   begin
      loop
         accept Eat_A_Meal(Customer : PERSON) do
            Put(Customer);
            Put_Line(" is ordering at the restaurant");
            delay 0.5 * HOURS;
            Put(Customer);
            Put_Line(" is eating at the restaurant");
            delay 0.5 * HOURS;
         end Eat_A_Meal;
      end loop;
   end Restaurant;

   task body Burger_Boy is
   begin
      loop
         accept Eat_A_Meal(Customer : PERSON) do
            Put(Customer);
            Put_Line(" is ordering at the Burger Boy");
            delay 0.1 * HOURS;
            Put(Customer);
            Put_Line(" is eating at the Burger Boy");
            delay 0.1 * HOURS;
         end Eat_A_Meal;
      end loop;
   end Burger_Boy;

begin
   null;
end Meals1;




-- Result of execution

-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- 
-- (The program will halt due to deadlock.)

 

检查名为e_c28_p1的文件。Ada程序,将作为大纲的选择性会合,即使它不包含一个。这个程序由四个任务组成,每个任务描述比尔和约翰的一天,每个任务描述在餐厅和汉堡男孩快餐店(希望不是一家真正的餐厅)吃饭。在这个程序中,所有的延迟时间都定义为一个名为HOURS的常量,它实际上会导致每小时延迟一秒,从而将程序的执行速度提高到一个合理的水平。

在餐厅吃饭需要半个小时的服务,再花半个小时来吃,而汉堡男孩只需要十分之一小时的服务和十分之一的时间来吃。名为Bills_Day的任务要求他每隔一小时吃三顿饭,而名为Johns_Day的任务要求他连续吃两顿饭,然后等四个小时直到吃晚饭。另一个有趣的事实是,两个人在餐厅吃每顿饭都不关心他们需要等多久才能坐下来吃饭。无论对手头的Ada程序还是对问题的分析都没有关系,从来没有调用一个任务,因为这是完全合法的。最后,餐厅和汉堡男孩一次只能服务一个顾客,因为这是任务编程的方式。然而,它们可以在同一时间服务不同的客户。

 

吃饭很慢

你会注意到,通过查看延迟时间,约翰等了0.4个小时,先到达餐厅,然后总共花了一个小时吃东西。Bill等了一个小时,然后去了餐厅,但是必须排队等了0.4个小时才让John吃完,然后又花了一个小时才吃完。在这一小时,约翰已经回来了,正在等待再次被服务。它们继续相互干扰,最后每人吃了三顿饭。

 

死锁会发生

细心的学生将注意到,名为Bills_Day和Johns_Day的任务依次执行直到完成,并且没有进行其他调用。然而,这两款游戏分别名为Restaurant和Burger_Boy,每款游戏都包含一个无限循环,且从未真正完成。当前两个任务执行到完成时,系统将识别出没有正在运行的任务能够调用正在等待的accept语句。系统将识别这种情况,并在显示死锁已经发生的消息后,将由于死锁而终止程序。

我们仍然没有给出一个合适的终止程序的方法,但是我们将在本章的后面部分讨论。您应该花一些必要的时间来彻底理解这个程序,因为它将是本章中其余示例程序的基础。理解后,编译并执行它,看看编译器是否能识别死锁条件。

 

条件会合

Example program ------> e_c28_p2.ada

                                    -- Chapter 28 - Program 2
with Ada.Text_IO;
use Ada.Text_IO;

procedure Meals2 is

   HOURS : constant := 1;
   type PERSON is (BILL, JOHN);

   package Enum_IO is new Ada.Text_IO.Enumeration_IO(PERSON);
   use Enum_IO;

   task Bills_Day;

   task Johns_Day;

   task Restaurant is
      entry Eat_A_Meal(Customer : PERSON);
   end Restaurant;

   task Burger_Boy is
      entry Eat_A_Meal(Customer : PERSON);
   end Burger_Boy;

   task body Bills_Day is
      My_Name : PERSON := BILL;
   begin
      delay 1.0 * HOURS;
      select
         Restaurant.Eat_A_Meal(My_Name);
      else
         Burger_Boy.Eat_A_Meal(My_Name);
      end select;
      delay 1.0 * HOURS;
      select
         Restaurant.Eat_A_Meal(My_Name);
      or
         delay 0.1 * HOURS;
         Burger_Boy.Eat_A_Meal(My_Name);
      end select;
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Bills_Day;

   task body Johns_Day is
      My_Name : PERSON := JOHN;
   begin
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 4.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Johns_Day;

   task body Restaurant is
   begin
      loop
         accept Eat_A_Meal(Customer : PERSON) do
            Put(Customer);
            Put_Line(" is ordering at the restaurant");
            delay 0.5 * HOURS;
            Put(Customer);
            Put_Line(" is eating at the restaurant");
            delay 0.5 * HOURS;
         end Eat_A_Meal;
      end loop;
   end Restaurant;

   task body Burger_Boy is
   begin
      loop
         accept Eat_A_Meal(Customer : PERSON) do
            Put(Customer);
            Put_Line(" is ordering at the Burger Boy");
            delay 0.1 * HOURS;
            Put(Customer);
            Put_Line(" is eating at the Burger Boy");
            delay 0.1 * HOURS;
         end Eat_A_Meal;
      end loop;
   end Burger_Boy;

begin
   null;
end Meals2;




-- Result of execution

-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the Burger Boy
-- BILL is eating at the Burger Boy
-- JOHN is ordering at the restaurant
-- BILL is ordering at the Burger Boy
-- BILL is eating at the Burger Boy
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
--
-- (The program will halt due to deadlock.)

 

检查名为e_c28_p2的程序。第一个条件集合的例子是Ada。这个程序与最后一个名为e_c28_p1的程序相同。除了在名为Bills_Day的任务中添加了一些语句。在这个节目中,比尔有点急于吃他的前两顿饭,并提出了一些条件来表明这一点。

 

选择的会合

当比尔开始一天的工作时,他很不耐烦,第一顿饭根本不会等。如果餐厅没有立即提供食物,他就会去汉堡男孩吃早餐。第29行到第33行中的语句替换了最后一个程序的单个语句来表示这种不耐烦。在Ada术语中,如果名为Restaurant的任务没有在Eat_A_Meal入口点等待,那么调用将不会在那里进行,而是调用Burger_Boy任务中的名为Eat_A_Meal入口点。如果Burger_Boy任务没有在入口点等待,那么Bill将被要求等待,直到它准备好,这时他将被服务。

保留词else用于指示所选的集合,它可以跟随任意数量的或子句。一般形式是;

    select 
         <entry call>;
    or
         >entry call>;
    else
         <entry call>;
    end select;

如果没有其他入口点在各自的会合点等待,则使用else子句。

 

推迟会合

比尔在吃第二顿饭的时候不太饿,所以他愿意在餐厅等上一小段时间,但如果他在十分之一小时内没有得到服务,他就会去汉堡男孩吃午餐。第35行到第40行中的单个语句替换了前一个程序中的单个语句。在Ada术语中,名为Bills_Day的任务将等待1个小时,以等待Restaurant任务到达它的入口点,之后它将调用Burger_Boy的入口点并等待,不管它在那里需要多长时间。

可以使用任意数量的选择,并在任意数量的选择中使用延迟语句。一般形式由;

    select 
         delay <time>;
         <entry call>;
    or 
         delay <time>;
         <entry call>;
    or
         delay <time>;
         <entry call>;
    end select;

如果使用了延迟语句,则不能使用else子句,因为else子句将立即执行,任何有延迟的语句都不会超时。因此,它永远不会被执行,必须被视为不可执行的代码。

 

比尔吃晚饭的顽固决心

比尔决定无论如何都不去汉堡男孩吃晚饭。他将等待任何必要的时间,以便在餐厅被服务,这反映在第42行,以完全相同的方式在前一个节目中表明。

请注意,这两个选择语句都位于调用任务中,而不是被调用的任务中,但是我们很快就会看到,在被调用的任务中也有相同类型的内容。

对行刑结果的检查将揭示比尔确实在汉堡男孩吃了他的两顿早餐会。此外,你会看到他的第二顿饭是在汉堡男孩点的,并且在约翰在餐厅吃饭的时候吃的。这表明资源得到了很好的利用。

请确保编译并执行此程序。观察执行的结果,看看它确实会让比尔偶尔去汉堡男孩。因为我们仍然没有优雅的终止,死锁再次发生。

 

延迟的入口点

Example program ------> e_c28_p3.ada

                                    -- Chapter 28 - Program 3
with Ada.Text_IO;
use Ada.Text_IO;

procedure Meals3 is

   HOURS : constant := 1;
   type PERSON is (BILL, JOHN);

   package Enum_IO is new Ada.Text_IO.Enumeration_IO(PERSON);
   use Enum_IO;

   task Bills_Day;

   task Johns_Day;

   task Restaurant is
      entry Eat_A_Meal(Customer : PERSON);
   end Restaurant;

   task Burger_Boy is
      entry Eat_A_Meal(Customer : PERSON);
   end Burger_Boy;

   task body Bills_Day is
      My_Name : PERSON := BILL;
   begin
      delay 1.0 * HOURS;
      select
         Restaurant.Eat_A_Meal(My_Name);
      else
         Burger_Boy.Eat_A_Meal(My_Name);
      end select;
      delay 1.0 * HOURS;
      select
         Restaurant.Eat_A_Meal(My_Name);
      or
         delay 0.1 * HOURS;
         Burger_Boy.Eat_A_Meal(My_Name);
      end select;
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Bills_Day;

   task body Johns_Day is
      My_Name : PERSON := JOHN;
   begin
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 4.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Johns_Day;

   task body Restaurant is
   begin
      loop
         select
            accept Eat_A_Meal(Customer : PERSON) do
               Put(Customer);
               Put_Line(" is ordering at the restaurant");
               delay 0.5 * HOURS;
               Put(Customer);
               Put_Line(" is eating at the restaurant");
               delay 0.5 * HOURS;
            end Eat_A_Meal;
         or
            delay 1.5 * HOURS;
            Put_Line("The restaurant is closed for the day");
            exit;
         end select;
      end loop;
   end Restaurant;

   task body Burger_Boy is
   begin
      loop
         select
            accept Eat_A_Meal(Customer : PERSON) do
               Put(Customer);
               Put_Line(" is ordering at the Burger Boy");
               delay 0.1 * HOURS;
               Put(Customer);
               Put_Line(" is eating at the Burger Boy");
               delay 0.1 * HOURS;
            end Eat_A_Meal;
         or
            delay 2.1 * HOURS;
            Put_Line("The Burger Boy is closed for the day");
            exit;
         end select;
      end loop;
   end Burger_Boy;

begin
   null;
end Meals3;




-- Result of execution

-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the Burger Boy
-- BILL is eating at the Burger Boy
-- JOHN is ordering at the restaurant
-- BILL is ordering at the Burger Boy
-- BILL is eating at the Burger Boy
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- The Burger Boy is closed for the day
-- The restaurant is closed for the day

 

检查名为e_c28_p3的程序。这是比尔和约翰满足他们对营养的渴望的另一个例子。在这种情况下,餐厅不会永远营业,汉堡男孩也不会,如果一段时间没有顾客,两者都会选择关闭一天。

第59行到第72行中的选择语句包含两个选项,一个是像往常一样处理客户,另一个是延迟1.5小时,然后是餐馆关门声明。如果1.5秒内没有对名为Eat_A_Meal的条目调用(因为HOURS实际上是1秒),那么将执行选择语句的第二个分支,结果是显示一条消息并执行一个退出语句。您应该记得,退出将带您离开最直接的循环,并从那里继续执行语句。在Restaurant任务的情况下,该任务因此被完成,并等待其他任务完成。

名为Burger_Boy的任务具有相同的备选入口点,只是延迟2.1秒。它也到达它的end语句并等待其他语句完成。对执行结果的检查将揭示,这两家餐厅实际上都达到了暂停和关闭的一天,让约翰挨饿,因为他从来没有吃他的第三顿饭。由于约翰没有吃他一天的第三顿饭,一些代码没有执行,也没有这样报告。这似乎是一个错误,但它实际上不是,因为它是一个任务错误,没有传播到主程序。这是根据Ada的定义,并在本章的最后两段进行了更全面的解释。

一定要编写和运行这个程序,并观察餐饮场所的提前关闭。

 

任务的有序终止

Example program ------> e_c28_p4.ada

                                    -- Chapter 28 - Program 4
with Ada.Text_IO;
use Ada.Text_IO;

procedure Terminat is

   HOURS : constant := 1;
   type PERSON is (BILL, JOHN);

   package Enum_IO is new Ada.Text_IO.Enumeration_IO(PERSON);
   use Enum_IO;

   task Bills_Day;

   task Johns_Day;

   task Restaurant is
      entry Eat_A_Meal(Customer : PERSON);
   end Restaurant;

   task Burger_Boy is
      entry Eat_A_Meal(Customer : PERSON);
   end Burger_Boy;

   task body Bills_Day is
      My_Name : PERSON := BILL;
   begin
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 1.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Bills_Day;

   task body Johns_Day is
      My_Name : PERSON := JOHN;
   begin
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 0.4 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
      delay 4.0 * HOURS;
      Restaurant.Eat_A_Meal(My_Name);
   end Johns_Day;

   task body Restaurant is
   begin
      loop
         select
            accept Eat_A_Meal(Customer : PERSON) do
               Put(Customer);
               Put_Line(" is ordering at the restaurant");
               delay 0.5 * HOURS;
               Put(Customer);
               Put_Line(" is eating at the restaurant");
               delay 0.5 * HOURS;
            end Eat_A_Meal;
         or
            terminate;
         end select;
      end loop;
   end Restaurant;

   task body Burger_Boy is
   begin
      loop
         select
            accept Eat_A_Meal(Customer : PERSON) do
               Put(Customer);
               Put_Line(" is ordering at the Burger Boy");
               delay 0.1 * HOURS;
               Put(Customer);
               Put_Line(" is eating at the Burger Boy");
               delay 0.1 * HOURS;
            end Eat_A_Meal;
         or
            terminate;
         end select;
      end loop;
   end Burger_Boy;

begin
   null;
end Terminat;




-- Result of execution

-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- Bill is eating at the restaurant
-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- BILL is ordering at the restaurant
-- BILL is eating at the restaurant
-- JOHN is ordering at the restaurant
-- JOHN is eating at the restaurant

 

检查名为e_c28_p4的程序。以Ada为例说明任务的有序终止。这个程序与本章中的第一个程序e_c28_p1相同。艾达,除了两个小变化。命名为Restaurant的任务的入口点被放置在一个选择语句中,其方式类似于e_c28_p2。ada或e_c28_p3。但使用了一个不同的或子句,其中包含保留词terminate。每次通过循环时,都可以选择名为Eat_A_Meal的入口点执行,任务执行将一直等待,直到调用该入口点。当程序等待entry调用时,带有terminate的select分支允许发生终止的可能性。然而,终止只能在正确的条件下发生。正确的条件要求所有其他并发任务,包括主程序中的任务,要么在它们的最终端等待终止,要么有一个类似的终止选择。

名为Burger_Boy的任务也有类似的终止选项。当两个调用任务到达它们的最终终端时,两个服务任务有可用的终止选项,因此有一个有序的终止,程序返回到操作系统。确保编译并执行这个程序,然后研究结果是否符合这个定义。

 

中断的声明

abort语句将无条件地终止任何任务,而不考虑它们当前的操作条件。这是极其突然的,而且不提供一个有序的终止。因此,只能在紧急情况下使用。电量损耗的检测可能就是这样一种情况。在可执行语句中,在合法使用可执行语句的任何地方,要中止的任务都列在保留字abort之后。

 

两个任务属性

有两个有用的任务属性可用于查找任何给定任务的条件。它们分别是CALLABLE和TERMINATED,每个都返回一个BOOLEAN类型,表示附加属性的任务的当前状态。有关这两个属性使用的详细信息,请参见ARM。

 

任务异常

在任何通信错误期间都会引发名为Tasking_Error的任务异常。任务精化期间的任何错误都会引发此异常(这将在后面的示例程序中进行说明)。如果被调用的任务被中止,则在调用方中引发此异常,并在已中止任务的输入队列上的所有调用的发起者中引发此异常。

如果在集合期间引发异常,则将其传播到两个任务中,因为每个任务都可能需要某种形式的恢复。如果异常未由任务在内部处理,则不会将其传播到调用程序,因为这样做将非常具有破坏性。这意味着一个看起来工作正常的程序实际上可能有一个非常破坏性的任务错误,就像名为e_c28_p3.ada的示例程序一样这一章。因此,每个重要的任务都应该有一个异常处理程序来防止未知异常的发生。

 

编程练习

1。修改名为e_c28_p2的程序。ada,使其在完成时有序终止。(Solution)

2。修改e_c28_p2。ada甚至进一步说,约翰每顿饭都在汉堡男孩吃,除非他必须在那里等待,在这种情况下,他在餐厅吃。(Solution)

 

 

---------------------------------------------------------------------------------------------------------------------------

原英文版出处:https://perso.telecom-paristech.fr/pautet/Ada95/a95list.htm

翻译(百度):博客园  一个默默的 *** 的人

posted @ 2021-04-26 21:23  yangjianfeng  阅读(45)  评论(0编辑  收藏  举报