Ada 95 Quality and Style Guide Chapter 6
Concurrency exists as either apparent concurrency or real concurrency.
In a single processor environment, apparent concurrency is the
result of interleaved execution of concurrent activities. In a
multiprocessor environment, real concurrency is the result of
overlapped execution of concurrent activities.
Concurrent programming is more difficult and error prone than
sequential programming. The concurrent programming features of
Ada are designed to make it easier to write and maintain concurrent
programs that behave consistently and predictably and avoid such
problems as deadlock and starvation. The language features themselves
cannot guarantee that programs have these desirable properties.
They must be used with discipline and care, a process supported
by the guidelines in this chapter.
The correct usage of Ada concurrency features results in reliable,
reusable, and portable software. Protected objects (added in Ada
95) encapsulate and provide synchronized access to their private
data (Rationale 1995, §II.9). Protected objects help you
manage shared data without incurring a performance penalty. Tasks
model concurrent activities and use the rendezvous to synchronize
between cooperating concurrent tasks. Much of the synchronization
required between tasks involves data synchronization, which can
be accomplished most efficiently, in general, using protected
objects. Misuse of language features results in software that
is unverifiable and difficult to reuse or port. For example,
using task priorities or delays to manage synchronization is not
portable. It is also important that a reusable component not
make assumptions about the order or speed of task execution (i.e.,
about the compiler's tasking implementation).
Although concurrent features such as tasks and protected objects
are supported by the core Ada language, care should be taken when
using these features with implementations that do not specifically
support Annex D
(Real-Time Systems). If Annex D is not specifically supported,
features required for real-time applications might not be implemented.
Guidelines in this chapter are frequently worded "consider
. . ." because hard and fast rules cannot apply in all situations.
The specific choice you make in a given situation involves design
tradeoffs. The rationale for these guidelines is intended to
give you insight into some of these tradeoffs.
6.1 CONCURRENCY OPTIONS
Many problems map naturally to a concurrent programming solution.
By understanding and correctly using the Ada language concurrency
features, you can produce solutions that are largely independent
of target implementation. Tasks provide a means, within the Ada
language, of expressing concurrent, asynchronous threads of control
and relieving programmers from the problem of explicitly controlling
multiple concurrent activities. Protected objects serve as a building
block to support other synchronization paradigms.
Tasks cooperate to perform the required activities of the software.
Synchronization and mutual exclusion are required between individual
tasks. The Ada rendezvous and protected objects provide powerful
mechanisms for both synchronization and mutual exclusion.
6.1.1 Protected Objects
- Consider using protected objects to provide mutually exclusive
access to data.
- Consider using protected objects to control or synchronize
access to data shared by multiple tasks.
- Consider using protected objects to implement synchronization,
such as a passive resource monitor.
- Consider encapsulating protected objects in the private
part or body of a package.
- Consider using a protected procedure to implement an interrupt
- Do not attach a protected procedure handler to a hardware interrupt
if that interrupt has a maximum priority greater than the ceiling
priority assigned to the handler.
- Avoid the use of global variables in entry barriers.
- Avoid the use of barrier expressions with side effects.
- Use tasks to model selected asynchronous
threads of control within the problem
- Consider using tasks to define concurrent
- Consider using rendezvous when your application requires synchronous
- Consider using discriminants to minimize the need for an explicit
(Rationale 1995, §9.1).
- Consider using discriminants to control composite components
of the protected objects, including
setting the size of an entry family (Rationale 1995, §9.1).
- Consider using a discriminant to set the priority of a protected
object (Rationale 1995, §9.1).
- Consider using a discriminant to identify an interrupt to a
protected object (Rationale 1995, §9.1).
- Consider declaring a task type with a discriminant to indicate
(Rationale 1995, §9.6):
- Priority, storage size, and size of entry families of individual
tasks of a type
- Data associated with a task (through an access discriminant)
6.1.4 Anonymous Task Types and Protected Types
- Consider using single task declarations to declare unique instances
of concurrent tasks.
- Consider using single protected declarations to declare unique
instances of protected objects.
6.1.5 Dynamic Tasks
- Minimize dynamic creation of tasks because of the potentially
high startup overhead; reuse tasks by having them wait for new
work on some appropriate entry queue.
- Do not rely on pragma Priority
unless your compiler supports the Real-Time Annex (Ada Reference
Manual 1995, Annex D) and priority scheduling.
- Minimize risk of priority inversion by use of protected objects
and ceiling priority.
- Do not rely upon task priorities to achieve a particular sequence
of task execution.
6.1.7 Delay Statements
- Do not depend on a particular delay being
achievable (Nissen and Wallis 1984).
- Use a delay until not a delay statement to
delay until a specific time has been reached.
- Avoid using a busy waiting loop
instead of a delay.
6.1.8 Extensibility and Concurrent Structures
- Carefully consider the placement of components of protected
types within a tagged type inheritance hierarchy.
- Consider using generics to provide extensibility of data types
requiring the restrictions provided by protected objects.
- You can declare the root as a limited tagged type with a component
that belongs to a protected type and give the tagged type primitive
operations that work by invoking the protected operations of that
- Given a tagged type implementing an abstract data type (perhaps
resulting from several extensions), you can declare a protected
type with a component belonging to the tagged type. The body of
each protected operation would then invoke the corresponding operation
of the abstract data type. The protected operations provide mutual
- You can use a hybrid approach where you declare a protected
type with a component of some tagged type. You then use this protected
type to implement a new root tagged type (not a descendant of
the original tagged type).
The need for tasks to communicate gives rise to most of the problems
that make concurrent programming so difficult. Used properly,
Ada's intertask communication features can improve the reliability
of concurrent programs; used thoughtlessly, they can introduce
subtle errors that can be difficult to detect and correct.
6.2.1 Efficient Task Communication
- Minimize the work performed during a rendezvous.
- Minimize the work performed in the selective
accept loop of a task.
- Consider using protected objects for data synchronization and
6.2.2 Defensive Task Communication
- Provide a handler for exception
whenever you cannot avoid a selective
accept statement whose alternatives
can all be closed (Honeywell 1986).
- Make systematic use of handlers for Tasking_Error.
- Be prepared to handle exceptions during a rendezvous.
- Consider using a when others exception handler.
Chapter 6 Continued