A reactive system is event-driven, maintains a permanent interaction with its environment, and executes at a rate determined by the environment[8]. Reactive systems are assumed to execute with performance sufficient to ensure they are never overdriven by their environment. Because under normal circumstances a reactive system never terminates, reactive systems cannot be characterized as a simple function from a single initial input state to a single final output state. Real-time systems are reactive systems with the addition of timing constraints. Operating systems are inherently reactive and provide the archetypical example of large reactive systems[9, 10].
The term reactive is more specific than the informal term event-driven, which is widely used and overloaded. For instance, an event-driven program may calculate a simple transformation and terminate. The term reactive is more general than soft real-time and near real-time, because a reactive system does not address any real-time constraints but only correct causality ordering[11].
Simple reactive systems are often programmed as explicit finite state machines, with external events driving the machine through state transitions. Explicitly coded state machines work well for problems with fewer than around 10 states. Above this size, explicitly programming a single state machine becomes difficult. Nonetheless, state machines are often used for device drivers, as this size suffices for many driver architectures. In this case a component such as an I/O supervisor usually has responsibility both for executing the state machines and instantiating the state machines needed to deal with physical concurrency due to multiple devices.
Programmers are often provided with special languages for integrating state machines into their program source. State Notation Language (SNL) is typical of these languages[12]. SNL is used for I/O intensive control systems and is compatible with C system programming.
Figure 1: A SNL Code Fragment
An example SNL program fragment is shown in Figure 1. This code turns a light on when a voltage exceeds 5 volts and off when the voltage falls below 3 volts. Examination of this code fragment illustrates how large programs developed in this manner suffer from hidden `spaghetti gotos'. Statement <state light_off {...} state light_on> effectively terminates in a <goto light_on>. The when() clauses guard the following code-block whenever the appropriate state has been entered, that is, the code-block will not execute until the corresponding guard is true.
SNL uses a `run-time sequencer' responsible for evaluating all the guards and serializing execution of ready code-blocks. SNL provides very good integration of the state machine, I/O, and conventional C code. SNL applications with between 10 and 20 states are considered to be `extremely complex.'[12] However, an SNL sequencer may control execution of multiple independent state machines, on occasion controlling concurrent execution of as many as 10 state machines. SNL is considered easy to understand and use.
For slightly larger applications, such as stand-alone industrial controllers, some means of providing hierarchical structuring of state machines is required. A typical industrial system, intended to support up to around 150 states, is the Action-State diagram[13]. This approach resembles coupling decision tables and state transition diagrams. The decision tables are compiled into hierarchical tables of action-routine addresses. A work-loop then sequentially executes action routines in response to events and the current state. This approach works well for controllers or drivers that do not require internal competitive concurrency.
Larger reactive systems that must support both competitive and cooperative concurrency are usually based on conventional operating system kernels providing multithreading and critical sections. This approach introduces nondeterminism and its associated concurrent programming problems. For large hard real-time systems that must guarantee predictable performance, cyclic executives are arguably still the preferable architecture. A cyclic system uses precomputed deterministic schedules designed for the worst case. While effective, the overhead of such pessimistic systems can be high, as code executes on a rigid table-driven timeline even if not needed. Under sustained near-worst-case conditions, however, nondeterministic systems that must expend run-time overhead scheduling their activities are less efficient than cyclic systems[14].