ADA 95教程 高级特性 简单的会合

任务之间的通信

在上一章中,我们运行了一些带有任务处理的程序,但是它们都使用了没有同步形式的简单任务处理。在使用任务的真实情况下,任务之间需要某种形式的交流,所以我们将在本章研究简单的会合。

Example program ------> e_c27_p1.ada

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

procedure HotDog is

   task Gourmet is
      entry Make_A_Hot_Dog;
   end Gourmet;

   task body Gourmet is
   begin
      Put_Line("I am ready to make a hot dog for you");
      for Index in 1..4 loop
         accept Make_A_Hot_Dog do
            delay 0.8;
            Put("Put hot dog in bun ");
            Put_Line("and add mustard");
         end Make_A_Hot_Dog;
      end loop;
      Put_Line("I am out of hot dogs");
   end Gourmet;

begin
   for Index in 1..4 loop
      Gourmet.Make_A_Hot_Dog;
      delay 0.1;
      Put_Line("Eat the resulting hot dog");
      New_Line;
   end loop;
   Put_Line("I am not hungry any longer");
end HotDog;


-- Result of execution

-- I am ready to make a hot dog for you
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- Put hot dog in bun and add mustard
-- I am out of hot dogs
-- Eat the resulting hot dog
--
-- I am not hungry any longer

 

检查名为e_c27_p1的程序。艾达来演示一下简单的集合。这个程序由两个任务组成,第7行到第22行和主程序本身。

 

entry语句

这里的任务规范比前一章的任何示例程序都要复杂一些,因为我们在任务中声明了一个入口点。保留字entry 从条目声明开始,后面是条目名称,在本例中为Make_A_Hot_Dog。entry语句定义到任务外部的接口,其方式与过程头在包规范中定义到包的外部接口非常相似。与包不同,任务规范中不允许声明类型、变量或常量,但是不限制允许的入口点的数量。条目可以将正式形参声明为条目名称的一部分(我们将在下一个示例程序中给出一个例子),但它不能具有类似于函数的返回形参。但是,允许一个或多个形式参数具有mode out或in out参数,因此在实现同步时,数据可以通过两种方式传递。也允许使用in模式。我们将在本章后面对这个主题有更多的讨论。

 

接受声明

任务体是一个非常简单的语句序列,其中一条消息输出到监视器,执行4次循环,然后输出另一条消息。这里的新东西是循环中的accept语句。accept语句以保留字accept开始,具有以下一般形式;

    accept <entry-name> do  
           <executable statements> 
    end <entry-name>;

Ada的任何法律声明都可以包含在里面。当任务执行到达accept语句时,任务“进入睡眠状态”,直到其他任务调用这个特定的accept语句。术语“进入睡眠”意味着任务不是简单地坐在那里,在等待时执行不做任何事情的循环,从而有效地浪费了系统的资源。相反,在被一个入口调用唤醒之前,它实际上什么也不做。也可以有一个不包含任何语句的接受语句。它的形式如下:

accept <entry-name>;

此语句仅用于任务同步,因为没有数据传递给输入的任务。

 

ENTRY调用

该程序的主要部分是另一个循环,它包含四个迭代,然后是一个语句,用于在监视器上显示一行文本。该循环唯一不寻常的地方是第26行语句,它是对名为Gourmet的任务中名为Make_A_Hot_Dog的条目的条目调用。请记住,这是两个并行运行的任务,我们将仔细解释发生了什么。

条目调用在第26行执行,它唤醒第15行处于睡眠状态的任务,而名为Gourmet的任务从它进入睡眠的地方继续。请注意,调用程序并不控制Gourmet的执行,但是Gourmet本身现在可以控制,因为它已经被允许继续操作。入口调用不像过程调用,操作序列在过程中继续,而只是一个同步调用,允许Gourmet在进入睡眠之前继续它正在做的事情。

 

调用程序现在在做什么?

在被调用的任务在它的accept块内执行语句的时间内,调用的任务被有效地置于睡眠状态,并且必须等待被调用的任务完成它的accept块。当美食家到达第19行时,两项任务被允许再次并行运行,直到其中一项或两项再次到达会合点。如果主程序在Gourmet准备好接受调用之前到达它的入口调用,那么主程序将等待直到Gourmet准备好。因此,accept语句和相应的入口调用用于同步两个任务。

如果将Make_A_Hot_Dog的末尾移动到accept语句之后的行(当然包括一个空语句),那么输出语句将并行运行。延迟被选择的方式是这样的,在做出这个改变之后,热狗将在它被制作之前被吃掉。这是本章末尾的一个编程练习。

 

这里必须提出几个要点

尽管入口调用看起来非常像过程调用,但它是不同的,因为对任务使用use子句是不合法的。因此,要求每个条目调用都必须使用扩展命名约定,或“点分”符号。重命名可以用来减少所使用的名称的大小,在下一个程序中将进行说明。

您将注意到,这两个任务恰好由4次调用和4次接受语句的执行组成。在我们的研究中,这样做是为了简化任务终止的问题。关于任务终止的研究将在下一章中详细讨论。在此之前,您应该可以看到当两个循环不同时会发生什么。将Gourmet任务中的循环更改为5次迭代,看看如果它等待接受一个从未到来的调用会发生什么,然后让主程序中的循环变大,看看当没有东西可以接受它的调用时会发生什么。我们将在本教程的后面一章研究任务异常。

在accept语句中执行return语句(此处未说明)对应于到达最终终点,并有效地终止了会合。

 

服务和用户

任务分为两个定义比较松散的组,服务任务和用户任务。服务任务是接受其他任务调用的任务,而用户任务是调用一个或多个服务任务的任务。在本示例程序中,Gourmet是一个服务任务,而主程序是一个用户任务。有些任务既接受调用又进行调用,它们被称为中间任务。关于Ada的文献中可能会用到这个术语,或者其他一些自定义的术语,因此您应该注意可能存在这样的分类。

一定要编译并执行这个程序,做出前面建议的更改,并观察结果。

 

多个入口点

Example program ------> e_c27_p2.ada

                                   -- Chapter 27 - Program 2
with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;

procedure HotDogs is

   task Gourmet is
      entry Make_A_Hot_Dog(Serial_Number : INTEGER);
   end Gourmet;

   procedure Get_Dog(Serial_Number : INTEGER) renames
                                          Gourmet.Make_A_Hot_Dog;

   task body Gourmet is
   begin
      Put_Line("I am ready to make a hot dog for you");

      accept Make_A_Hot_Dog(Serial_Number : INTEGER) do
         Put_Line("This will be the first hot dog");
         Put("Put hot dog in bun ");
         Put_Line("and add mustard");
         delay 0.8;
      end Make_A_Hot_Dog;

      for Index in 1..4 loop
         accept Make_A_Hot_Dog(Serial_Number : INTEGER) do
            Put("This will be hot dog number");
            Put(Serial_Number, 3);
            New_Line;
            Put("Put hot dog in bun ");
            Put_Line("and add mustard");
            delay 0.8;
         end Make_A_Hot_Dog;
      end loop;

      accept Make_A_Hot_Dog(Serial_Number : INTEGER) do
         Put_Line("This will be the last hot dog");
         Put("Put hot dog in bun ");
         Put_Line("and add mustard");
         delay 0.8;
      end Make_A_Hot_Dog;

      Put_Line("I am out of hot dogs");
   end Gourmet;

begin
   for Index in 1..6 loop
      Get_Dog(Index);
      Put_Line("Eat the resulting hot dog");
      New_Line;
   end loop;
   Put_Line("I am not hungry any longer");
end HotDogs;


-- Result of execution

-- I am ready to make a hot dog for you
-- This will be the first hot dog
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- This will be hot dog number  2
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- This will be hot dog number  3
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- This will be hot dog number  4
-- Put hot dog in bun and add mustard
-- I am out of hot dogs
-- Eat the resulting hot dog
--
-- This will be hot dog number  5
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- This will be the last hot dog
-- Put hot dog in bun and add mustard
-- Eat the resulting hot dog
--
-- I am not hungry any longer

 

检查名为e_c27_p2的程序。Ada添加了一些以多个accept 语句开始的任务主题。您将注意到,在任务主体中有三个accept语句对应于任务规范中声明的单个入口点。对于accept语句的数量没有限制,当逻辑导致依次执行每个语句时,它们将被执行。请记住,任务是按照语句序列中定义的逻辑顺序执行的,除非每次逻辑导致执行到达一个accept语句时,任务将“进入睡眠状态”,直到对等待中的accept语句进行入口调用。此外,逻辑并不关心条目调用来自哪里,也不知道它来自哪里,它只关心进行了条目调用。因此,可能会有几个不同的任务调用这个入口点,并允许任务通过其逻辑继续进行。因为这个程序中只有另一个任务,所以我们确切地知道入口调用是在哪里生成的。

仔细研究这个逻辑就会发现,必须调用第18行中的accept语句,然后是对第26行的4次调用,以及对第36行的另一次调用。在对该入口点进行6次调用之后,任务将到达其执行的终点并将被完成。您将注意到,我们通过主程序的任务对这个入口点进行了6次调用,所以一切都是正确的。

 

重命名任务条目

在主程序任务中使用在第11行声明的替代名称来说明重命名功能的使用。注意,在这种情况下不需要点符号。同样,您可以更改调用数或接受数,以查看异常错误或死锁条件。

 

输入参数

到目前为止,您应该已经注意到,每个accept语句都有一个与之相关联的形参,其方式与子程序相同。实际上,可以有任意多的参数来将数据从调用任务传输到被调用任务,或者反向传输。所有三种参数传递模式都是合法使用的,如果没有像这里所做的那样指定参数传递模式,则使用in模式。然而,在任务中有一个不同之处,你不允许像函数一样有返回参数,但你可以通过使用out或in out参数返回值。

由于可以通过两种方式传递参数,因此任务看起来就像一个过程一样被执行,但正如前面提到的,其中有非常明确的区别。过程实际上是由调用程序执行的,但任务只是被释放,让它与调用程序并行地独立运行。这个任务不是一个从属程序,而是运行它自己的逻辑,正如它被编码的那样。

请确保在理解程序要做什么之后编译并执行该程序。

 

多个调用任务

Example program ------> e_c27_p3.ada

                                   -- Chapter 27 - Program 3
with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;

procedure ManyDogs is

   task Gourmet is
      entry Make_A_Hot_Dog(Serial_Number : INTEGER;
                           With_Mustard  : BOOLEAN);
   end Gourmet;

   task body Gourmet is
   begin
      Put_Line("I am ready to make a hot dog for you");
      for Index in 1..5 loop
         accept Make_A_Hot_Dog(Serial_Number : INTEGER;
                               With_Mustard  : BOOLEAN) do
            Put("Put hot dog number");
            Put(Serial_Number, 2);
            Put(" in bun ");
            if With_Mustard then
               Put_Line("and add mustard");
            else
               Put_Line("and hold the mustard");
            end if;
            delay 0.8;
         end Make_A_Hot_Dog;
      end loop;
      Put_Line("I am out of hot dogs");
   end Gourmet;

   task Bill;
   task John;

   task body Bill is
   begin
      for Index in 1..3 loop
         Gourmet.Make_A_Hot_Dog(Index, FALSE);
         Put_Line("Bill is eating the hot dog without mustard");
         New_Line;
      end loop;
      Put_Line("Bill is not hungry any longer");
   end Bill;

   task body John is
   begin
      for Index in 1..2 loop
         Gourmet.Make_A_Hot_Dog(Index, TRUE);
         Put_Line("John is eating the hot dog with mustard");
         New_Line;
      end loop;
      Put_Line("John is not hungry any longer");
   end John;

begin
   null;
end ManyDogs;


-- Result of execution

-- I am ready to make a hot dog for you
-- Put hot dog number 1 in bun and add mustard
-- Put hot dog number 1 in bun and hold the mustard
-- John is eating the hot dog with mustard
--
-- Put hot dog number 2 in bun and add mustard
-- Bill is eating the hot dog without mustard
--
-- Put hot dog number 2 in bun and hold the mustard
-- John is eating the hot dog with mustard
--
-- John is not hungry any longer
-- Bill is eating the hot dog without mustard
--
-- Put hot dog number 3 in bun and hold the mustard
-- I am out of hot dogs
-- Bill is eating the hot dog without mustard
--
-- Bill is not hungry any longer

 

检查名为e_c27_p3的程序。两个任务调用第三个任务的入口点。名为Gourmet的任务只有一个入口点,但这次在任务规范中声明了两个正式参数,在接受语句中声明了同样的两个参数。当然,这些必须达成一致。第16行中名为Make_A_Hot_Dog的入口点包含一些要显示的文本和0.8秒的延迟,因为制作一个a hot dog需要一点时间。你会注意到,有时加了芥末,有时没有,这取决于谁吃的结果。稍后我们将对此进行更多讨论。

在第35行到第53行中,我们有两个额外的任务,分别是Bill和John,每个任务并行执行,每个任务请求大量的热狗,并在零时间内消耗它们,因为在它们的下一个请求之前没有延迟。您还会注意到,由于Bill要求3个热狗,而John要求2个,而提供热狗的任务正好是5个。稍后我们将讨论更好的任务终止方法。就目前而言,接受这里描述的乌托邦式的情况就好了。

 

入口在入口点堆积

考虑到我们到目前为止所讲的关于任务分配的所有内容,你应该明白,这三个任务同时开始执行,Bill和John都要求立即得到一个热狗。因为只有一个入口点,所以一次只能提供一个入口点,Ada定义没有指定首先服务哪个入口点。它确实指定了最终将为两者提供服务,因此在入口点有一个隐式队列,在请求可以被依次服务之前,可以在该队列中存储请求。所有请求都以先进先出(FIFO)为基础进行服务,不考虑任务的优先级(稍后定义)。对于程序员来说,这个队列是一个“隐藏”的队列,因为您无法访问它。因此,您不能对条目进行排序,并重新定义各种条目请求的执行顺序。可以使用名为COUNT的属性,它将返回任何输入队列上挂起的请求数量。它的用法将在名为e_c29_p6的程序中进行说明。Ada在本教程的第29章。

在我们学习了一些高级任务主题之后,您将能够定义几个具有不同优先级的队列,并根据需要设置自己的优先级。

 

输出的顺序有点奇怪

回到手边的程序。检查执行的结果会发现,由于某些未知的原因,John的任务第一个提出请求,Gourmet的任务第一个提出a hot dog,因为John的任务是要求a hot dog 的任务。然而,在John被允许继续执行之前,Gourmet任务继续进行,服务于它的入口队列中的Make_A_Hot_Dog的下一个调用,并为Bill做了一个没有芥末的热狗。这时Gourmet已经完成了所有的输入调用,并允许运行一个其他任务。名为John的任务被允许继续执行。约翰吃了他的热狗,又要了一个。又一次,通过某种未定义的方法,当Bill还没有被允许吃他的第一个热狗的时候,这个名为Gourmet的任务被允许运行并给John做了第二个热狗,加了芥末。这一过程一直持续到所有条件都满足为止,这意味着五个热狗已经做好,并且全部吃完。两个消耗任务都表示他们没有饥饿,而名为Gourmet的任务则表示没有热狗了。程序终于完成了。

 

那么结果的有趣顺序呢?

尽管结果的顺序似乎很有趣,但他们确实遵守了我们制定的所有规则。请记住,作为一名有经验的程序员,您习惯于看到每件事情都以非常明确的顺序出现,因为您的编程生涯都是在编写顺序程序。如果从每个任务的角度来看这个输出,您将看到每个任务的输出都是完全顺序的,正如任务逻辑所定义的那样。此外,您将看到执行的顺序被保留为由各种集合定义的,因为没有人在热狗制作之前吃,也没有热狗制作得太早。任务的同步已经完全按照我们的要求完成了。

花足够的时间研究这里的逻辑,完全理解正在发生的事情,然后编译并执行这个程序,看看您的编译器是否以不同的顺序执行任何事情。

 

select语句

Example program ------> e_c27_p4.ada

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

procedure Retail1 is

   Number_Of_Dogs : INTEGER := 0;

   task Retail_Hot_Dogs is
      entry Stock_With_A_Hot_Dog;
      entry Deliver_A_Hot_Dog;
   end Retail_Hot_Dogs;

   task body Retail_Hot_Dogs is
   begin
      accept Stock_With_A_Hot_Dog do
         Put_Line("Put the first hot dog on the shelf");
         Number_Of_Dogs := Number_Of_Dogs + 1;
      end Stock_With_A_Hot_Dog;

      for Index in 1..7 loop
         Put("In loop => ");
         select
            accept Stock_With_A_Hot_Dog do
               Put_Line("Add a hot dog to the shelf");
               Number_Of_Dogs := Number_Of_Dogs + 1;
            end Stock_With_A_Hot_Dog;
         or
            accept Deliver_A_Hot_Dog do
               Put_Line("Remove a hot dog from the shelf");
               Number_Of_Dogs := Number_Of_Dogs - 1;
            end Deliver_A_Hot_Dog;
         end select;
      end loop;
   end Retail_Hot_Dogs;

begin
   for Index in 1..4 loop
      Retail_Hot_Dogs.Stock_With_A_Hot_Dog;
      Retail_Hot_Dogs.Deliver_A_Hot_Dog;
   end loop;
end Retail1;




-- Result of execution

-- Put the first hot dog on the shelf
-- In loop => Remove a hot dog from the shelf
-- In loop => Add a hot dog to the shelf
-- In loop => Remove a hot dog from the shelf
-- In loop => Add a hot dog to the shelf
-- In loop => Remove a hot dog from the shelf
-- In loop => Add a hot dog to the shelf
-- In loop => Remove a hot dog from the shelf

 检查名为e_c27_p4的程序。Ada是我们第一个使用select语句的示例程序。select语句用于允许任务在两个或多个可选入口点之间进行选择。实际上,一个任务可以在两个或多个入口点等待一个入口调用,并且可以对第一个出现的入口调用进行操作。select语句的结构由:

    select 
         accept ...;    -- Complete entry point logic
    or
         accept ...;    -- Complete entry point logic
    or
         accept ...;    -- Complete entry point logic
    end select;

并在本示例程序的第23行到第33行中进行了说明。在这种情况下,有两个选择选项,但是可以包含的选择的数量没有限制。每个附加分支由保留字或分隔。当任务的程序控制到达第23行中的select语句时,任何一个入口调用都可以被接受并立即执行。

 

整个程序

常识告诉我们,在货架上摆满热狗之前,我们不能运送热狗,因此编写的程序反映了这一点。该任务需要在使用select语句开始循环之前调用Stock_With_A_Hot_Dog条目,以确保至少有一个热狗可用。在此之后,它就不关心条目调用的顺序了,因为循环中的select语句将允许它们以任何顺序发生。这是在Ada任务中设置优先级要求的一种非常简单的方法,但是它太简单而不能真正有效,我们将在检查可能发生的一些问题时看到这一点。

首先,如果主程序,或任务,不能叫Stock_With_A_Hot_Dog第一,系统会锁定与调用程序要求交付热狗和坚决拒绝继续直到它,和被称为任务拒绝交付一个直到它已经有一个在行16。由于两个任务都拒绝执行任何操作,系统处于死锁状态。可以通过反转第39行和第40行中的两个调用来模拟这种情况。您的编译器可能会给出一条消息,指示已发生死锁并终止程序的操作。另一个问题与这个程序的不灵活性有关,因为我们再次计算了完成两个任务所需的调用数和两个任务中编程的兼容编号。 

一个更深层的问题是,在一个热狗被储存之后,没有什么能阻止我们在不增加更多的热狗到货架上的情况下运送数百个热狗。

当你认为你理解了这个程序,编译并执行它,然后我们将继续下一个程序,我们将解决与当前程序有关的三个问题中的两个。

 

带有保护的选择语句

Example program ------> e_c27_p5.ada

检查名为e_c27_p5的程序。Ada是一个带有保护的选择语句的例子。守卫用于保护select语句的入口点,以防止在上一个程序中发生类似的愚蠢事件。在这个程序中,任务主体Retail_Hot_Dogs被修改为在第26行和第34行中包含用于第25行到第39行中选择语句的守卫。守卫只是一个布尔条件,在特定的入口点被接受并执行它的逻辑之前必须被满足。带保护的select语句的一般形式如下:

    select 
        when <BOOLEAN condition> => 
             accept ...;    -- Complete entry point logic 
    or 
        when <BOOLEAN condition> => 
             accept ...;    -- Complete entry point logic 
    or 
        when <BOOLEAN condition> => 
             accept ...;    -- Complete entry point logic 
    end select;

并且允许的选择的数量没有限制,每个选择都用保留字或分隔。事实上,一个或多个选择可以没有保护,在这种情况下,它类似于总是计算为TRUE的保护。当遇到选择语句时,每个守卫被计算为TRUE或FALSE,那些被计算为TRUE的条件允许进入活动等待状态,而那些被计算为FALSE的条件则不允许进入活动等待状态。对于那些将守卫值赋值为FALSE的变量,会被当作它们不存在一样对待。一旦在输入选择语句时计算了守卫,它们就不会被重新计算,直到下次遇到选择语句时才会重新计算,但是保持静态。

 

限制货架上热狗的数量

在这个程序中,当输入select语句在第25行,警卫在26行评估如果热狗在货架上的数量小于8,然后接受声明行27日启用,我们被允许在架子上一个热狗。如果热狗的数量大于零,就像第34行中的警卫为我们测试的那样,那么第35行中的accept语句将被启用,并且允许我们交付一个热狗。即使我们可以在货架上存放热狗,或者送热狗,我们也必须等到其他任务要求我们这样做之后才能真正执行其中一个操作。你应该清楚,在这种特殊情况下,我们总是被允许做至少一种手术,在许多情况下,两种手术都是被允许的。如果没有一个守卫值为TRUE,那么就不能进行任何选择,程序因此会有效地死锁,并引发名为Tasking_Error的异常。作为程序员,您可以捕获此异常的方式与捕获任何其他异常并以自己的方式处理它的方式大致相同,但处理任务异常与在顺序操作期间引发的异常的规则略有不同。稍后我们将详细讨论任务异常。

应该指出的是,即使守卫的计算结果为FALSE,也可以将条目添加到入口队列中,并在后续执行select语句时为其提供服务,此时守卫可能变为TRUE。由于这种定义入口队列的方法,对入口的调用不会丢失,并且操作是可预测的。

这个程序包含四个任务,对主程序进行计数,其中一个任务名为Five_Dogs,由于延迟时间较短,它很快地在货架上存放了5个热狗,另一个任务删除5个热狗的速度稍慢一些。由于循环内建的相对较长的时间延迟,主程序储存和取回四个热狗相当缓慢。

 

看警卫的工作

当您运行这个程序时,您将看到很少的警卫操作,因为警卫限制的选择。5个热狗很快就被放上了货架,但是从来没有达到8个的上限,而且货架上总是有热狗来满足有限的需求。事实上,如执行结果所示,货架上的数量永远不会超过5个,而且总是超过0个。您应该编译并执行该程序,以查看您的编译器是否与用于此执行的编译器做相同的事情。

更改第26行,使限制为3,并重新编译和执行结果程序。在这种情况下,您将非常清楚地看到,第一个防护措施防止超过三个热狗被放在货架上。实际上,它为Stock_With_A_Hot_Dog入口点构建了入口队列,并要求供应商等待货架空间。

将第46行和第54行中的延迟反转(如您的下一个练习),以便热狗的消耗速度远远快于它们的存放速度,以便需要名为Deliver_A_Hot_Dog的入口点上的警卫来保护太多热狗的运送。在这种情况下,到达此入口点的队列将在交付热狗时构建一个要满足的请求列表。

 

这个节目好多了

这个程序解决了上一个程序所列出的三个问题中的两个,但是我们仍然必须使用计数所需的入口调用并提供适当数目的入口的方法。正如之前承诺的,这个问题将很快得到解决。请确保编译并执行此程序三次,一次未修改,两次使用建议的更改,然后研究输出以确保完全理解它。

 

存储器的一种受保护的区域

Example program ------> e_c27_p6.ada

                                   -- Chapter 27 - Program 6

with Ada.Text_IO, Ada.Integer_Text_IO;
use Ada.Text_IO, Ada.Integer_Text_IO;

procedure Protect is
   
protected Animals is
   procedure Add_Some(Dogs_In : INTEGER; Cats_IN : INTEGER);
   procedure Subtract_Some(Dogs_In : INTEGER; Cats_In : INTEGER);
   procedure Get_Count(Dogs_Out : out INTEGER; 
                                           Cats_Out : out INTEGER);
private
   Dogs : INTEGER := 5;
   Cats : INTEGER := 3;
end Animals;

protected body Animals is
 
   procedure Add_Some(Dogs_In : INTEGER; Cats_In : INTEGER) is
   begin
      Dogs := Dogs + Dogs_In;
      Cats := Cats + Cats_In;
   end Add_Some;

   procedure Subtract_Some(Dogs_In : INTEGER; Cats_In : INTEGER) is
   begin
      Dogs := Dogs - Dogs_In;
      Cats := Cats - Cats_In;
   end Subtract_Some;

   procedure Get_Count(Dogs_Out : out INTEGER; 
                                           Cats_Out : out INTEGER) is
   begin
      Dogs_Out := Dogs;
      Cats_Out := Cats;
   end Get_Count;

end Animals;


   task Farm_Animals;
   task City_Animals;
   task Catch_Animals;

   task body Farm_Animals is
   begin
      for Index in 1..5 loop
         delay(0.3);
         Animals.Subtract_Some(3, 2);
      end loop;
   end Farm_Animals;

   task body City_Animals is
   begin
      for Index in 1..4 loop
         delay(0.5);
         Animals.Subtract_Some(1, 2);
      end loop;
   end City_Animals;

   task body Catch_Animals is
   begin
      for Index in 1..7 loop
         delay(0.6);
         Animals.Add_Some(7, 4);
      end loop;
   end Catch_Animals;

Number_Of_Dogs : INTEGER;
Number_Of_Cats : INTEGER;

begin
   for Index in 1..12 loop
      Animals.Get_Count(Number_Of_Dogs, Number_Of_Cats);
      Put("Dog count = ");
      Put(Number_Of_Dogs, 4);
      Put(" Cat count = ");
      Put(Number_Of_Cats, 4);
      New_Line;
      delay 0.5;
   end loop;
end Protect;


-- Result of execution

-- Dog count =    5 Cat count =    3
-- Dog count =    1 Cat count =   -1
-- Dog count =    1 Cat count =   -3
-- Dog count =    4 Cat count =   -3
-- Dog count =    7 Cat count =   -3
-- Dog count =   14 Cat count =    1
-- Dog count =   21 Cat count =    5
-- Dog count =   21 Cat count =    5
-- Dog count =   28 Cat count =    9
-- Dog count =   35 Cat count =   13
-- Dog count =   35 Cat count =   13
-- Dog count =   35 Cat count =   13

 

示例程序名为e_c27_p6。Ada给出了一个非常简单的数据保护示例。如果有多个任务都在向同一条记录写入,那么可以想象,在一个任务完成写入之前,另一个任务得到控制并开始向同一条记录写入。这将导致损坏的数据,这是编写有多个任务的程序时的一个主要问题。第8行到第39行中的受保护区域定义了三个过程,它们的操作方式与我们在本教程中使用的任何其他过程一样,只是它们不受多个条目的保护。不可能有多个任务同时在这三个过程中执行代码,因为这是它们的目的。如果有多个任务请求对这三个过程的调用,那么其中一个任务将进入它所调用的过程,而其他任务将在它们所调用的过程之外等待,直到第一个任务离开它所调用的过程。这可以防止数据损坏。

程序的其余部分很简单,由三个任务和主程序组成,主程序共同工作,从公共池中简单地添加和减去动物。注意,所有数据必须在规范的私有部分中,不允许在受保护代码的受保护主体中。

 

函数有点不同

这些过程被允许读取或写入私有数据,但是一个函数只被允许有in mode参数,并且它只被允许读取私有部分中的数据。由于只允许读取数据,所以多个任务可以同时读取私有数据,但不能在另一个任务在过程中执行代码时读取,因为它可能正在写入内部数据。这允许对read的多次调用,解决了读者和作者的经典问题。

受保护的部分比我们在这里介绍的要灵活得多,但这将为您理解它的用途提供一个良好的开端。

 

还有一个保留字

保留字requeue在一个受保护的块中使用,用来代表调用程序调用另一个子程序。执行所需代码的条件可能还不存在,因此它会被重新排队,直到条件合适为止。这是一个非常高级的技巧,你可以在需要的时候自学。要达到有效使用这种技术所需的专业水平可能需要很长时间。

 

编程练习

1.移动e_c27_p1中的accept语句的末尾。ada到line后立即接受语句本身,以看到它是可能吃热狗之前,因为任务是同时运行的(Solution)

2.在e_c27_p5中添加另一个任务。ada执行循环10次,延迟0.3秒,输出当前货架上热狗的数量。(Solution)

3.使用Ada包。练习2中定义的新过程每次输出货架上热狗的数量时,都输出经过的时间。(Solution)

 

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

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

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

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