Rationale for Ada 2005: Tasking and Real-Time

RUSTOP
BACKNEXT

ENG

3. Synchronized interfaces

@ We now turn to the most important improvement to the core tasking features introduced by Ada 2005. This concerns the coupling of object oriented and real-time features through inheritance.

@ Recall from the paper on the object oriented model that we can declare an interface thus

  1        type Int is interface;

@ An interface is essentially an abstract tagged type that cannot have any components but can have abstract operations and null procedures. We can then derive other interfaces and tagged types by inheritance such as

  1        type Another_Int is interface and Int1 and Int2;
  2        type T is new Int1 and Int2;
  3        type TT is new T and Int3 and Int4;

@ Remember that a tagged type can be derived from at most one other normal tagged type but can also be derived from several interfaces. In the list, the first is called the parent (it can be a normal tagged type or an interface) and any others (which can only be interfaces) are called progenitors.

@ Ada 2005 also introduces further categories of interfaces, namely synchronized, protected, and task interfaces. A synchronized interface can be implemented by either a task or protected type; a protected interface can only be implemented by a protected type and a task interface can only be implemented by a task type.

@ A nonlimited interface can only be implemented by a nonlimited type. However, an explicitly marked limited interface can be implemented by any tagged type (limited or not) or by a protected or task type. Remember that task and protected types are inherently limited. Note that we use the term limited interface to refer collectively to interfaces marked limited, synchronized, task or protected and we use explicitly limited to refer to those actually marked as limited.

@ So we can write

  1        type LI is limited interface; -- similarly type LI2
  2        type SI is synchronized interface;
  3        type TI is task interface;
  4        type PI is protected interface;

@ and we can of course provide operations which must be abstract or null. (Remember that synchronized is a new reserved word.) We can compose these interfaces provided that no conflict arises. The following are all permitted:

  1        type TI2 is task interface and LI and TI;
  2        type LI3 is limited interface and LI and LI2;
  3        type TI3 is task interface and LI and LI2;
  4        type SI2 is synchronized interface and LI and SI;

@ The rule is simply that we can compose two or more interfaces provided that we do not mix task and protected interfaces and the resulting interface must be not earlier in the hierarchy: limited, synchronized, task/protected than any of the ancestor interfaces.

@ We can derive a real task type or protected type from one or more of the appropriate interfaces

  1        task type TT is new TI with
  2                ... -- and here we give entries as usual
  3        end TT;

@ or

  1        protected type PT is new LI and SI with
  2                ...
  3        end PT;

@ Unlike tagged record types we cannot derive a task or protected type from another task or protected type as well. So the derivation hierarchy can only be one level deep once we declare an actual task or protected type.

@ The operations of these various interfaces are declared in the usual way and an interface composed of several interfaces has the operations of all of them with the same rules regarding duplication and overriding of an abstract operation by a null one and so on as for normal tagged types.

@ When we declare an actual task or protected type then we must implement all of the operations of the interfaces concerned. This can be done in two ways, either by declaring an entry or protected operation in the specification of the task or protected object or by declaring a distinct subprogram in the same list of declarations (but not both). Of course, if an operation is null then it can be inherited or overridden as usual.

@ Thus the interface

  1        package Pkg is
  2                type TI is task interface;
  3                procedure P (X : in TI) is abstract;
  4                procedure Q (X : in TI; I : in Integer) is null;
  5        end Pkg;

@ could be implemented by

  1        package PT1 is
  2                task type TT1 is new TI with
  3                        entry P; -- P and Q implemented by entries
  4                        entry Q (I : in Integer);
  5                end TT1;
  6        end PT1;

@ or by

  1        package PT2 is
  2                task type TT2 is new TI with
  3                        entry P; -- P implemented by an entry
  4                end TT2;
  5                -- Q implemented by a procedure
  6                procedure Q (X : in TT2; I : in Integer);
  7        end PT2;

@ or even by

  1        package PT3 is
  2                task type TT3 is new TI with end;
  3                        -- P implemented by a procedure
  4                        -- Q inherited as a null procedure
  5                procedure P (X : in TT3);
  6        end PT3;

@ In this last case there are no entries and so we have the juxtaposition with end which is somewhat similar to the juxtaposition is end that occurs with generic packages used as signatures.

@ Observe how the first parameter which denotes the task is omitted if it is implemented by an entry.

@ This echoes the new prefixed notation for calling operations of tagged types in general. Remember that rather than writing

  1        Op (X, Y, Z, ...);

@ we can write

  1        X.Op (Y, Z, ...);

@ provided certain conditions hold such as that X is of a tagged type and that Op is a primitive operation of that type.

@ In order for the implementation of an interface operation by an entry of a task type or a protected operation of a protected type to be possible some fairly obvious conditions must be satisfied.

@ In all cases the first parameter of the interface operation must be of the task type or protected type (it may be an access parameter).

@ In addition, in the case of a protected type, the first parameter of an operation implemented by a protected procedure or entry must have mode out or in out (and in the case of an access parameter it must be an access to variable parameter).

@ If the operation does not fit these rules then it has to be implemented as a subprogram. An important example is that a function has to be implemented as a function in the case of a task type because there is no such thing as a function entry. However, a function can often be directly implemented as a protected function in the case of a protected type.

@ Entries and protected operations which implement inherited operations may be in the visible part or private part of the task or protected type in the same way as for tagged record types.

@ It may seem rather odd that an operation can be implemented by a subprogram that is not part of the task or protected type itself – it seems as if it might not be task safe in some way. But a common paradigm is where an operation as an abstraction has to be implemented by two or more entry calls.

@ An example occurs in some implementations of the classic readers and writers problem as we shall see later.

@ Of course a task or protected type which implements an interface can have additional entries and operations as well just as a derived tagged type can have more operations than its parent.

@ The overriding indicators overriding and not overriding can be applied to entries as well as to procedures. Thus the package PT2 above could be written as

  1        package PT2 is
  2                task type TT2 is new TI with
  3                        overriding -- P implemented by an entry
  4                        entry P;
  5                end TT2;
  6                overriding -- Q implemented by procedure
  7                procedure Q (X : in TT2; I : in Integer);
  8        end PT2;

@ We will now explore a simple readers and writers example in order to illustrate various points. We start with the following interface

  1        package RWP is
  2                type RW is limited interface;
  3                procedure Write (Obj : out RW; X : in Item) is abstract;
  4                procedure Read (Obj : in RW; X : out Item) is abstract;
  5        end RWP;

@ The intention here is that the interface describes the abstraction of providing an encapsulation of a hidden location and a means of writing a value (of some type Item) to it and reading a value from it – very trivial.

@ We could implement this in a nonsynchronized manner thus

  1        type Simple_RW is new RW with
  2                record
  3                        V : Item;
  4                end record;
  5        overriding
  6        procedure Write (Obj : out Simple_RW; X : in Item);
  7        overriding
  8        procedure Read (Obj : in Simple_RW; X : out Item);
  9        ...
 10        procedure Write (Obj : out Simple_RW; X : in Item) is
 11        begin
 12                Obj.V := X;
 13        end Write;
 14        procedure Read (Obj : in Simple_RW; X : out Item) is
 15        begin
 16                X := Obj.V;
 17        end Read;

@ This implementation is of course not task safe (task safe is sometimes referred to as thread-safe). If a task calls Write and the type Item is a composite type and the writing task is interrupted part of the way through writing, then a task which calls Read might get a curious result consisting of part of the new value and part of the old value.

@ For illustration we could derive a synchronized interface type Sync_RW is synchronized interface and RW; This interface can only be implemented by a task or protected type. For a protected type we might have

  1        protected type Prot_RW is new Sync_RW with
  2                overriding
  3                procedure Write (X : in Item);
  4                overriding
  5                procedure Read (X : out Item);
  6        private
  7                V : Item;
  8        end;
  9        protected body Prot_RW is
 10                procedure Write (X : in Item) is
 11                begin
 12                        V := X;
 13                end Write;
 14                procedure Read (X : out Item) is
 15                begin
 16                        X := V;
 17                end Read;
 18        end Prot_RW;

@ Again observe how the first parameter of the interface operations is omitted when they are implemented by protected operations.

@ This implementation is perfectly task safe. However, one of the characteristics of the readers and writers example is that it is quite safe to allow multiple readers since they cannot interfere with each other. But the type Prot_RW does not allow multiple readers because protected procedures can only be executed by one task at a time.

@ Now consider

  1        protected type Multi_Prot_RW is new Sync_RW with
  2                overriding
  3                procedure Write (X : in Item);
  4                not overriding
  5                function Read return Item;
  6        private
  7                V : Item;
  8        end;
  9
 10        overriding
 11        procedure Read (Obj : in Multi_Prot_RW; X : out Item);
 12        ...
 13        protected body Multi_Prot_RW is
 14                procedure Write (X : in Item) is
 15                begin
 16                        V := X;
 17                end Write;
 18                function Read return Item is
 19                begin
 20                        return V;
 21                end Read;
 22        end Multi_Prot_RW;
 23
 24        procedure Read (Obj : in Multi_Prot_RW; X : out Item) is
 25        begin
 26                X := Obj.Read;
 27        end Read;

@ In this implementation the procedure Read is implemented by a procedure outside the protected type and this procedure then calls the function Read within the protected type. This allows multiple readers because one of the characteristics of protected functions is that multiple execution is permitted (but of course calls of the protected procedure Write are locked out while any calls of the protected function are in progress). The structure is emphasized by the use of overriding indicators.

@ A simple tasking implementation might be as follows

  1        task type Task_RW is new Sync_RW with
  2                overriding entry Write (X : in Item);
  3                overriding entry Read (X : out Item);
  4        end;
  5
  6        task body Task_RW is
  7                V : Item;
  8        begin
  9                loop
 10                        select
 11                                accept Write (X : in Item) do
 12                                        V := X;
 13                                end Write;
 14                        or
 15                                accept Read (X : out Item) do
 16                                        X := V;
 17                                end Read;
 18                        or
 19                                terminate;
 20                        end select;
 21                end loop;
 22        end Task_RW;

@ Finally, here is a tasking implementation which allows multiple readers and ensures that an initial value is set by only allowing a call of Write first. It is based on an example in that textbook [3].

  1        task type Multi_Task_RW (V : access Item) is new Sync_RW with
  2                overriding entry Write (X : in Item);
  3                not overriding entry Start;
  4                not overriding entry Stop;
  5        end;
  6        overriding procedure Read (Obj : in Multi_Task_RW; X : out Item);
  7        ...
  8        task body Multi_Task_RW is
  9                Readers : Integer := 0;
 10        begin
 11                accept Write (X : in Item) do
 12                        V.all := X;
 13                end Write;
 14                loop
 15                        select
 16                                when Write'Count = 0 =>
 17                                        accept Start;
 18                                                Readers := Readers + 1;
 19                        or
 20                                        accept Stop;
 21                                                Readers := Readers1;
 22                        or
 23                                when Readers = 0 =>
 24                                        accept Write (X: in Item) do
 25                                                V.all := X;
 26                                        end Write;
 27                        or
 28                                        terminate;
 29                        end select;
 30                end loop;
 31        end Multi_Task_RW;
 32        overriding procedure Read (Obj : in Multi_Task_RW; X : out Item) is
 33        begin
 34                Obj.Start;
 35                X := Obj.V.all;
 36                Obj.Stop;
 37        end Read;

@ In this case the data being protected is accessed via the access discriminant of the task. It is structured this way so that the procedure Read can read the data directly. Note also that the procedure Read (which is the implementation of the procedure Read of the interface) calls two entries of the task.

@ It should be observed that this last example is by way of illustration only. As is well known, the Count attribute used in tasks (as opposed to protected objects) can be misleading if tasks are aborted or if entry calls are timed out. Moreover, it would be gruesomely slow.

@ So we have seen that a limited interface such as RW might be implemented by a normal tagged type (plus its various operations) and by a protected type and also by a task type. We could then dispatch to the operations of any of these according to the tag of the type concerned. Observe that task and protected types are now other forms of tagged types and so we have to be careful to say tagged record type (or informally, normal tagged type) where appropriate.

@ In the above example, the types Simple_RW, Prot_RW, Multi_Prot_RW, Task_RW and Multi_Task_RW all implement the interface RW.

@ So we might have

  1        RW_Ptr: access RW'Class := ...
  2        ...
  3        RW_Ptr.Write(An_Item); -- dispatches

@ and according to the value in RW_Ptr this might call the appropriate entry or procedure of an object of any of the types implementing the interface RW.

@ However if we have

  1        Sync_RW_Ptr : access Sync_RW'Class := ...

@ then we know that any implementation of the synchronized interface Sync_RW will be task safe because it can only be implemented by a task or protected type. So the dispatching call

  1        Sync_RW_Ptr.Write (An_Item); -- task safe dispatching

@ will be task safe.

@ An interesting point is that because a dispatching call might be to an entry or to a procedure we now permit what appear to be procedure calls in timed entry calls if they might dispatch to an entry.

@ So we could have

  1        select
  2                RW_Ptr.Read (An_Item); -- dispatches
  3        or
  4                delay Seconds (10);
  5        end select;

@ Of course it might dispatch to the procedure Read if the type concerned turns out to be Simple_RW in which case a time out could not occur. But if it dispatched to the entry Read of the type Task_RW then it could time out.

@ On the other hand we are not allowed to use a timed call if it is statically known to be a procedure.

@ So

  1        A_Simple_Object : Simple_RW;
  2        ...
  3        select
  4                A_Simple_Object.Read(An_Item); -- illegal
  5        or
  6                delay Seconds(10);
  7        end select;

@ is not permitted.

@ A note of caution is in order. Remember that the time out is to when the call gets accepted. If it dispatches to Multi_Task_RW.Read then time out never happens because the Read itself is a procedure and gets called at once. However, behind the scenes it calls two entries and so could take a long time. But if we called the two entries directly with timed calls then we would get a time out if there were a lethargic writer in progress. So the wrapper distorts the abstraction. In a sense this is not much worse than the problem we have anyway that a time out is to when a call is accepted and not to when it returns – it could hardly be otherwise.

@ The same rules apply to conditional entry calls and also to asynchronous select statements where the triggering statement can be a dispatching call.

@ In a similar way we also permit timed calls on entries renamed as procedures. But note that we do not allow timed calls on generic formal subprograms even though they might be implemented as entries.

@ Another important point to note is that we can as usual assume the common properties of the class concerned. Thus in the case of a task interface we know that it must be implemented by a task and so the operations such as abort and the attributes Identity, Callable and so on can be applied. If we know that an interface is synchronized then we do know that it has to be implemented by a task or a protected type and so is task safe.

@ Typically an interface is implemented by a task or protected type but it can also be implemented by a singleton task or protected object despite the fact that singletons have no type name. Thus we might have

  1        protected An_RW is new Sync_RW with
  2                procedure Write (X : in Item);
  3                procedure Read (X : out Item);
  4        end;

@ with the obvious body. However we could not declare a single protected object similar to the type Multi_Prot_RW above. This is because we need a type name in order to declare the overriding procedure Read outside the protected object. So singleton implementations are possible provided that the interface can be implemented directly by the task or protected object without external subprograms.

@ Here is another example

  1        type Map is protected interface;
  2        procedure Put (M : Map; K : Key; V : Value) is abstract;

@ can be implemented by

  1        protected A_Map is new Map with
  2                procedure Put (K : Key; V : Value);
  3                ...
  4        end A_Map;

@ There is a fairly obvious rule about private types and synchronized interfaces. Both partial and full view must be synchronized or not. Thus if we wrote

  1        type SI is synchronized interface;
  2        type T is new SI with private;

@ then the full type T has to be a task type or protected type or possibly a synchronized, protected or task interface.

@ We conclude this discussion on interfaces by saying a few words about the use of the word limited. (Much of this has already been explained in the paper on the object oriented model but it is worth repeating in the context of concurrent types.) We always explicitly insert limited, synchronized, task, or protected in the case of a limited interface in order to avoid confusion. So to derive a new explicitly limited interface from an existing limited interface LI we write

  1        type LI2 is limited interface and LI;

@ whereas in the case of normal types we can write

  1        type LT is limited ...
  2        type LT2 is new LT and LI with ... -- LT2 is limited

@ then LT2 is limited by the normal derivation rules. Types take their limitedness from their parent (the first one in the list, provided it is not a progenitor) and it does not have to be given explicitly on type derivation – although it can be in Ada 2005 thus

  1        type LT2 is limited new LT and LI with ...

@ Remember the important rule that all descendants of a nonlimited interface have to be nonlimited because otherwise limited types could end up with an assignment operation.

@ This means that we cannot write

  1        type NLI is interface; -- nonlimited
  2        type LI is limited interface; -- limited
  3        task type TT is new NLI and LI with ... -- illegal

@ This is illegal because the interface NLI in the declaration of the task type TT is not limited.

Rationale for Ada 2005: Tasking and Real-Time

@ENGRUSTOPBACKNEXT

3. Синхронизированные интерфейсы

@ Теперь мы обращаемся к самому важному усовершенствованию в области управления задачами введенному Адой 2005. Это касается связи объектно-ориентированных средств и средств работы в реальном времени через наследование.

@ Возвращаясь к статье описывающей объектно-ориентированную модель мы можем объявить интерфейс таким образом:

  1
  2        type Int is interface;

@ Интерфейс это по существу абстрактный теговый тип, который не может иметь никаких компонентов, но может иметь абстрактные операции и нулевые процедуры. Тогда мы можем получить другие интерфейсы и теговые типы через наследование:

  1        type Another_Int is interface and Int1 and Int2;
  2        type T is new Int1 and Int2;
  3        type TT is new T and Int3 and Int4;

@ Напомним, что теговый тип может быть унаследован, в основном, из другого нормального тегового типа, но может также быть получен из нескольких интерфейсов. В списке наследования первое место занимает предок называемый родителем (это может быть нормальный теговый тип или интерфейс), а другие предки занимающие последующие позиции (которые можут быть только интерфейсами), назваются прародителями.

@ Ада 2005 вводит новые категории интерфейсов именуемые sinchronized, protected, и task. Синхронизированный интерфейс может быть осуществлен или задачей или защищённым типом; защищенный интерфейс может быть осуществлен только защищенным типом, а интерфейс задачи может быть осуществлен только задачным типом.

@ Неограниченный интерфейс может быть осуществлен только неограниченным типом. Однако, явно отмеченный ограниченный интерфейс может быть осуществлен любым теговым типом (ограниченным или нет) или задачным типом или защищенным. Напомним, что задачи и защищенные типы неотъемлемо ограничены. Отметим, что мы используем термин ограниченный интерфейс чтобы обратиться ко всем интерфейсам отмеченным как limited, sinchronized, task или protected, и мы используем явно limited, чтобы обратиться к фактически отмеченным как ограниченным.

@ Таким образом, мы можем написать:

  1        type LI is limited interface; -- similarly type LI2
  2        type SI is synchronized interface;
  3        type TI is task interface;
  4        type PI is protected interface;

@ и мы можем обеспечить операции, которые должны быть абстрактными или нулевыми. (Напомним, что synchronized - новое зарезервированное слово). Мы можем составить эти интерфейсы при условии, что никакой конфликт не возникает. Следующее всё разрешено:

  1        type TI2 is task interface and LI and TI;
  2        type LI3 is limited interface and LI and LI2;
  3        type TI3 is task interface and LI and LI2;
  4        type SI2 is synchronized interface and LI and SI;

@ Существует простое правило которое гласит: чтобы составить два или более интерфейса необходимо чтобы мы не смешивали задачу и защищенные интерфейсы, и ни один из интерфейсов предков в иерархии не должен быть limited, synchronized, task или protected.

@ Мы можем получить реальный тип задачи или защищенный тип из одного или более соответствующих интерфейсов:

  1        task type TT is new TI with
  2                ... -- and here we give entries as usual
  3        end TT;

@ или

  1        protected type PT is new LI and SI with
  2                ...
  3        end PT;

@ В отличие от теговых типов записи мы не можем получить задачу или защищенный тип от другой задачи или защищенного типа также. Таким образом, иерархия наследования может быть только на один уровень глубже чем уровень на котором мы объявляем фактическую задачу или защищенный тип.

@ Операции этих различных интерфейсов объявляются обычным способом, и у интерфейса, составленного из нескольких интерфейсов, есть операции всех их с теми же самыми правилами относительно дублирования, и отмена абстрактной операции нулевым и так далее что касается нормальных теговых типов.

@ Когда мы объявляем фактическую задачу или защищенный тип, тогда мы должны осуществить все операции заинтересованных интерфейсов. Это может быть сделано двумя способами, или объявляя точку входа или защищенную операцию в спецификации задачи или защищённый объект или объявляя отличную подпрограмму в том же самом списке объявлений (но не оба вместе). Конечно, если операция - пустой указатель тогда, она может быть наследована или отменена (overridden) как обычно.

@ Таким образом, интерфейс:

  1        package Pkg is
  2                type TI is task interface;
  3                procedure P (X : in TI) is abstract;
  4                procedure Q (X : in TI; I : in Integer) is null;
  5        end Pkg;

@ может быть реализован так:

  1        package PT1 is
  2                task type TT1 is new TI with
  3                        entry P; -- P and Q implemented by entries
  4                        entry Q (I : in Integer);
  5                end TT1;
  6        end PT1;

@ или так:

  1        package PT2 is
  2                task type TT2 is new TI with
  3                        entry P; -- P implemented by an entry
  4                end TT2;
  5                -- Q implemented by a procedure
  6                procedure Q (X : in TT2; I : in Integer);
  7        end PT2;

@ или даже так:

  1        package PT3 is
  2                task type TT3 is new TI with end;
  3                        -- P implemented by a procedure
  4                        -- Q inherited as a null procedure
  5                procedure P (X : in TT3);
  6        end PT3;

@ В последнем случае нет никаких входов и, таким образом, у нас есть сопоставление с концом, который несколько подобен сопоставлению, конец, который происходит с generic пакетами, используемыми как сигнатуры.

@ Обратите внимание, как первый параметр, который обозначает задачу, опущен, если это осуществлено входом.

@ Это демонстрирует новую префиксную нотацию для вызова операции теговых типов вообще. Вместо того чтобы писать:

  1        Op (X, Y, Z, ...);

@ мы можем написать:

  1        X.Op (Y, Z, ...);

@ при условии что X - теговый тип и Op - примитивная операция этого типа.

@ Для реализации операции интерфейса посредством точки входа задачного типа или защищенной операцией защищенного типа необходимо соблюдение нескольких довольно очевидных условий.

@ Во всех случаях первый параметр операции интерфейса должен быть типом задачи или защищенным типом (это может быть ссылочный параметр).

@ Кроме того, в случае защищенного типа, у первого параметра операции, осуществленной защищенной процедурой или входом, должен быть режим или в (и в случае ссылочного параметра это должна быть ссылка на параметр переменную).

@ Если операция не соответствует этим правилам тогда, она должна быть осуществлена как подпрограмма. Важный пример - то, что функция должна быть осуществлена как функция в случае типа задачи, потому что нет такой вещи как функциональный вход. Однако, функция может часто непосредственно осуществляться как защищенная функция в случае защищенного типа.

@ Входы и защищенные операции, которые осуществляют наследованные операции, могут быть в видимой части или приватной части задачи или защищенного типа таким же образом это касается теговых типов записи.

@ Может казаться довольно странным, что операция может быть осуществлена подпрограммой, которая не является частью задачи или защищенного типа непосредственно - кажется, как будто это не может быть сейф задачи в некотором роде. Но общая парадигма состоит в том, где операция как абстракция должна быть осуществлена двумя или больше вызовами входа.

@ Пример происходит в некоторых реализациях классической проблемы читения и записи, которую мы будем рассматривать позже.

@ Конечно, у задачи или защищенного типа, который осуществляет интерфейс, могут быть дополнительные входы и операции также как у полученного тегового типа может быть больше операций чем у его родителя.

@ Замена индикаторов overriding и not overriding могут быть применены к входам так же как к процедурам. Таким образом, пакет PT2 рассмотренный выше мог бы быть написан как:

  1        package PT2 is
  2                task type TT2 is new TI with
  3                        overriding -- P implemented by an entry
  4                        entry P;
  5                end TT2;
  6                overriding -- Q implemented by procedure
  7                procedure Q (X : in TT2; I : in Integer);
  8        end PT2;

@ Теперь рассмотрим простой пример чтения и записи, чтобы проиллюстрировать различные аспекты. Мы начнём со следующего интерфейса:

  1        package RWP is
  2                type RW is limited interface;
  3                procedure Write (Obj : out RW; X : in Item) is abstract;
  4                procedure Read (Obj : in RW; X : out Item) is abstract;
  5        end RWP;

@ Цель здесь состоит в том, что интерфейс описывает абстракцию инкапсуляции скрытого местоположения и средств записи и чтения значения некоторого типа Item.

@ Мы могли осуществить это несинхронизированным способом так:

  1        type Simple_RW is new RW with
  2                record
  3                        V : Item;
  4                end record;
  5        overriding
  6        procedure Write (Obj : out Simple_RW; X : in Item);
  7        overriding
  8        procedure Read (Obj : in Simple_RW; X : out Item);
  9        ...
 10        procedure Write (Obj : out Simple_RW; X : in Item) is
 11        begin
 12                Obj.V := X;
 13        end Write;
 14        procedure Read (Obj : in Simple_RW; X : out Item) is
 15        begin
 16                X := Obj.V;
 17        end Read;

@ Эта реализация - конечно не сейф задачи (сейф задачи иногда упоминается как сейф потока). Если задача в момент вызова Write будет прервана в процессе изменения типа Item являющемся составным, и при этом будет вызвана функция Read может получится весьма любопытный результат, состоящий из части нового значения и части старого значения.

@ Для иллюстрации мы могли получить синхронизированный интерфейсный тип Sync_RW синхронизированный интерфейс и RW; Этот интерфейс может быть осуществлен только задачей или защищённым типом. Для защищенного типа мы могли бы иметь:

  1        protected type Prot_RW is new Sync_RW with
  2                overriding
  3                procedure Write (X : in Item);
  4                overriding
  5                procedure Read (X : out Item);
  6        private
  7                V : Item;
  8        end;
  9        protected body Prot_RW is
 10                procedure Write (X : in Item) is
 11                begin
 12                        V := X;
 13                end Write;
 14                procedure Read (X : out Item) is
 15                begin
 16                        X := V;
 17                end Read;
 18        end Prot_RW;

@ Обратите внимание, как первый параметр операций интерфейса опущен, когда они осуществлены защищенными операциями.

@ Эта реализация - отлично сейф задачи. Однако, одна из особенностей примера чтения и записи состоит в том, что весьма безопасно позволить многжественный доступ для чтения, так как они не могут навредить друг другу. Но тип Prot_RW не разрешает множественное чтение, потому что защищенные процедуры могут быть выполнены только одной задачей за один раз.

@ Теперь рассмотрим:

  1        protected type Multi_Prot_RW is new Sync_RW with
  2                overriding
  3                procedure Write (X : in Item);
  4                not overriding
  5                function Read return Item;
  6        private
  7                V : Item;
  8        end;
  9
 10        overriding
 11        procedure Read (Obj : in Multi_Prot_RW; X : out Item);
 12        ...
 13        protected body Multi_Prot_RW is
 14                procedure Write (X : in Item) is
 15                begin
 16                        V := X;
 17                end Write;
 18                function Read return Item is
 19                begin
 20                        return V;
 21                end Read;
 22        end Multi_Prot_RW;
 23
 24        procedure Read (Obj : in Multi_Prot_RW; X : out Item) is
 25        begin
 26                X := Obj.Read;
 27        end Read;

@ В этой реализации процедура Read осуществлена процедурой вне защищенного типа, и эта процедура тогда вызывает функцию Read в пределах защищенного типа. Это разрешает множественное чтение, потому что одна из особенностей защищенных функций состоит в том, что массовое обращение разрешено (но конечно запросы защищенной процедуры Write блокируются, в то время вызовов защищенной функции). Структура подчеркнута при помощи индикатора overriding.

@ Простая реализация управления задачами могла бы быть следующая:

  1        task type Task_RW is new Sync_RW with
  2                overriding entry Write (X : in Item);
  3                overriding entry Read (X : out Item);
  4        end;
  5
  6        task body Task_RW is
  7                V : Item;
  8        begin
  9                loop
 10                        select
 11                                accept Write (X : in Item) do
 12                                        V := X;
 13                                end Write;
 14                        or
 15                                accept Read (X : out Item) do
 16                                        X := V;
 17                                end Read;
 18                        or
 19                                terminate;
 20                        end select;
 21                end loop;
 22        end Task_RW;

@ Наконец, вот реализация управления задачами, которая разрешает множественное чтение и гарантирует, что начальное значение установлено разрешая сначала только вызов Write. Этот пример взят из учебника [3].

  1        task type Multi_Task_RW (V : access Item) is new Sync_RW with
  2                overriding entry Write (X : in Item);
  3                not overriding entry Start;
  4                not overriding entry Stop;
  5        end;
  6        overriding procedure Read (Obj : in Multi_Task_RW; X : out Item);
  7        ...
  8        task body Multi_Task_RW is
  9                Readers : Integer := 0;
 10        begin
 11                accept Write (X : in Item) do
 12                        V.all := X;
 13                end Write;
 14                loop
 15                        select
 16                                when Write'Count = 0 =>
 17                                        accept Start;
 18                                        Readers := Readers + 1;
 19                        or
 20                                        accept Stop;
 21                                        Readers := Readers1;
 22                        or
 23                                when Readers = 0 =>
 24                                        accept Write (X: in Item) do
 25                                                V.all := X;
 26                                        end Write;
 27                        or
 28                                        terminate;
 29                        end select;
 30                end loop;
 31        end Multi_Task_RW;
 32        overriding procedure Read (Obj : in Multi_Task_RW; X : out Item) is
 33        begin
 34                Obj.Start;
 35                X := Obj.V.all;
 36                Obj.Stop;
 37        end Read;

@ В этом случае к защищаемым данным обращаются через ссылочный дискриминант задачи. Это структурировано так чтобы процедура Read могла читать данные непосредственно. Отметим также, что процедура Read (которая является реализацией процедуры Read интерфейса) вызывает две точки входа задачи.

@ Заметим, что этот последний пример приведён только для иллюстрации. Как известно, атрибут Count, используемый в задачах (в противоположность защищенным объектам), может вводить в заблуждение, если задачи прерываются или если запросы входа установлены. Кроме того, это было бы ужасно медленно.

@ Таким образом мы видим, что ограниченный интерфейс, такой как RW мог бы быть осуществлен нормалным теговым типом (плюс его различные операции) и защищенным типом и также типом задачи. Мы могли тогда послать операциям любого из них согласно тэгу заинтересованного типа. Заметим, что задача и защищенные типы - теперь другие формы теговых типов и, таким образом, мы должны делать все возможное сказать теговый тип записи (или неофициально, нормалный теговый тип) где это возможно.

@ В вышеупомянутом примере, типы Simple_RW, Prot_RW, Multi_Prot_RW, Task_RW и Multi_Task_RW все реализация интерфейса RW.

@ Таким образом, мы могли бы иметь:

  1        RW_Ptr: access RW'Class := ...
  2        ...
  3        RW_Ptr.Write(An_Item); -- dispatches

@ и согласно значению в RW_Ptr это могло бы вызвать соответствующий вход или процедуру объекта любого из типов, осуществляя интерфейс RW.

@ Однако, если мы имеем:

  1        Sync_RW_Ptr : access Sync_RW'Class := ...

@ тогда мы знаем, что любая реализация синхронизированного интерфейса, Sync_RW будет сейфом задачи, потому что это может только быть осуществлено задачей или защищённым типом. Так запрос диспетчеризации:

  1        Sync_RW_Ptr.Write (An_Item); -- task safe dispatching

@ будет сейф задачи.

@ Интересный момент состоит в том, что, потому что запрос диспетчеризации мог бы быть к входу или к процедуре, мы теперь разрешаем то, что, кажется, вызовы процедуры в установленных запросах входа, если они могли бы послать входу.

@ Таким образом мы могли иметь:

  1        select
  2                RW_Ptr.Read (An_Item); -- dispatches
  3        or
  4                delay Seconds (10);
  5        end select;

@ Конечно это могло бы послать процедуре Read, если заинтересованный тип оказывается Simple_RW, когда time out не может произойти. Но если бы это послало вход Read типа Task_RW тогда, то это может произойти time out.

@ С другой стороны нам не разрешено использовать установленный вызов, если это, как статически известно, процедура.

@ Так:

  1        A_Simple_Object : Simple_RW;
  2        ...
  3        select
  4                A_Simple_Object.Read(An_Item); -- illegal
  5        or
  6                delay Seconds(10);
  7        end select;

@ не разрешено.

@ Примечание предостережения то, чтобы. Помните, что время - к тому, когда запрос принят. Если это посылает Multi_Task_RW.Read тогда, время никогда не случается, потому что само Read - процедура и вызвано сразу. Однако, негласно это вызывает два входа и, занимают много времени - также. Но если бы мы вызвали эти два входа непосредственно с установленными запросами тогда, то мы вывели бы время, если бы была летаргическая программа записи в продвижении. Таким образом обертка искажает абстракцию. В некотором смысле это не является намного худшим чем проблема, которую мы имеем так или иначе, который время - к тому, когда запрос принят а не к тому, когда это возвращается - это могло едва быть иначе.

@ Те же самые правила относятся к условным запросам входа и также к асинхронным операторам выбора, где оператор вызова может быть запросом диспетчеризации.

@ Подобным способом мы также разрешаем установленные запросы к входам, переименованным как процедуры. Но отметим, что мы не позволяем установленные запросы к универсальным формальным подпрограммам даже при том, что они могли бы быть осуществлены как входы.

@ Другой важный пункт состоит в том, что мы можем как обычно принять общие свойства заинтересованного класса. Таким образом, в случае интерфейса задачи мы знаем, что это должно быть осуществлено задачей и так операции, такие как аварийное прекращение работы, и атрибутов Identity, Callable и так далее, может быть применена. Если мы знаем, что интерфейс синхронизирован тогда, мы действительно знаем, что он должен быть осуществлен задачей или защищенным типом и сейф задачи - также.

@ Обычно интерфейс осуществляется задачей или защищённым типом, но он может также быть осуществлено задачей единичного предмета или защищённым объектом несмотря на то, что у единичных предметов нет никакого имени типа. Таким образом, мы могли бы иметь:

  1        protected An_RW is new Sync_RW with
  2                procedure Write (X : in Item);
  3                procedure Read (X : out Item);
  4        end;

@ с очевидным телом. Однако мы не могли объявить единственный защищенный объект, подобный типу Multi_Prot_RW рассмотренному выше. Это потому что мы нуждаемся в имени типа, чтобы объявить отмену (overriding) процедуру Read вне защищенного объекта. Таким образом, реализации единичного предмета возможны при условии, что интерфейс может быть осуществлен непосредственно задачей или защищённым объектом без внешних подпрограмм.

@ Вот другой пример:

  1        type Map is protected interface;
  2        procedure Put (M : Map; K : Key; V : Value) is abstract;

@ может быть реализован так:

  1        protected A_Map is new Map with
  2                procedure Put (K : Key; V : Value);
  3                ...
  4        end A_Map;

@ Есть довольно очевидное правило о приватных типах и синхронизированных интерфейсах. И частичное и полное представление должно быть синхронизировано или нет. Таким образом, если мы написали:

  1        type SI is synchronized interface;
  2        type T is new SI with private;

@ тогда полный тип T должен быть типом задачи или защищенным типом или возможно синхронизированным, защищенным или интерфейсом задачи.

@ Мы заключаем это обсуждение по интерфейсам, говоря несколько слов об использовании слова limited. (Большой части этого уже обсуждалось в статье посвящённой объектно-ориентированной модели, но это стоит повторить в контексте параллельных типов). Мы всегда явно вставляем limited, synchronized, task, или protected в случае ограниченного интерфейса, чтобы избежать беспорядка. Чтобы получить новый явно ограниченный интерфейс из существующего ограниченного интерфейса LI мы пишем:

  1        type LI2 is limited interface and LI;

@ тогда как в случае нормальных типов мы можем написать:

  1        type LT is limited ...
  2        type LT2 is new LT and LI with ... -- LT2 is limited

@ тогда LT2 ограничен по нормальным правилам наследования. Типы берут свою ограниченность (limitedness) от своего родителя (первый в списке, если это не прародитель), и это не должно быть дано явно при наследовании типа - хотя это может быть в Аде 2005 таким образом:

  1        type LT2 is limited new LT and LI with ...

@ Помните важное правило, что все потомки неограниченного интерфейса должны быть неограничены, потому что иначе ограниченные типы могли закончиться с операцией присваивания.

@ Это означает, что мы не можем написать:

  1        type NLI is interface; -- nonlimited
  2        type LI is limited interface; -- limited
  3        task type TT is new NLI and LI with ... -- illegal

@ Это незаконно, потому что интерфейс NLI в объявлении задачи типа TT не ограничен.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:55

. .