CS452 - Real-Time Programming - Winter 2015

Lecture 26 - Controlling Multiple Trains

Public Service Annoucements

  1. Second train demo: 17 July, 2015.
  2. Exam: 16.00 14 April to 18.30 15 April
  3. IMPORTANT. You can't make much progress on any of the goals for milestone 2 without knowing where the train is!


As we go down this list both pathology detection and the length of the edit-compile-test cycle grow without bound.

1. Deadlock

One or more tasks will never run again. For example

  1. Task sends to itself (local: rest of system keeps running, task itself will never run)
  2. Every task does Receive( ) (global: nothing is running, except possibly the idle task; all tasks are SEND_BL)
  3. Cycle of tasks sending around the cycle. All tasks in the cycle are RCV_BL. (local: other tasks keep running)
  4. One train is on a siding trying to get out; another train is on the switch at the end of the siding trying to get in. (external: application okay but the world is in an unanticipated configuration. "Unanticipated" means that no code was implemented to deal with this case.)

Kernel can detect the first three; only the train application can detect the fourth.

What can we do when an error is detected?

Potential deadlock can be detected at compile time


2. Livelock (Deadly Embrace)


Two or more tasks are READY. For each task, the state of the other tasks prevents progress being made regardless of which task is ACTIVE.

A higher level of coordination is required.

There are two types of livelock

  1. Ones that are the result of bad coding
  2. Ones that are inherent in the application definition

Looking for solutions we prefer ones that avoid a central planner. Why?

Livelock usually occurs in the context of resource contention

Livelock that's Really Deadlock


  1. Make a single compound resourse, BUT
  2. Impose a global order on resource requests that all clients must follow.
  3. Create a mega-server that handles all resource requests

Real Livelock

Proprietor1 & proprietor2 fail the requests

Livelock that's Really a Critical Race

We could try to make the clients a little more considerate

    while ( no resources ) {
      Send( prop1, getres1, result );
      while ( result == "sorry" ) {
         Delay( ... );
         Send( prop1, getres1, result );
      Send( prop2, getres2, result );
      if ( result == "sorry" ) {
        Send( prop1, relres1, ... );
        Delay( ... );
      } else {
This we call a critical race because avoiding what is effectively an infinite loop depends on the timing of execution.

Inherent Livelock

Remember the example where two trains come face to face, each waiting for the other to move. They will wait facing each other until the demo is over, probably polling.

What's hard about solving this problem?

In real life,

What's most easy for you to do is to programme each driver with

  1. detection, e.g.,
  2. work around, e.g.,

3. Critical Races


  1. Two tasks, A & B, at the same priority
  2. A is doing a lot of debugging IO
  3. B always reserves a section of track before A, and all is fine.
  4. Debugging IO is removed
  5. A reserves the section before B can get it, and execution collapses.
  6. Lower priority of A to the same level as C.
  7. Now C executes faster and gets a resource before D .
  8. You shuffle priorities forever, eventually reverting, to put back in the debugging IO.


The order in which computation is done is an important factor in determining whether or not it is successful.

Critical races, like Livelock can be


  1. Small changes in priorities change execution unpredictably, and drastically.
  2. Debugging output changes execution drastically.
  3. Changes in train speeds change execution drastically.

`Drastically' usually means chaos in both senses of the term

  1. Sense one: a small change in the initial conditions produces an exponentially growing divergence in the execution.
  2. Sense two: exercise for the reader.


  1. Explicit synchronization
  2. Gating is a technique of global synchronization

4. Performance

Changes in performance of one task with respect to another often give rise to critical races

The hardest problem to solve

In practice, how do you know you have performance problems? Problems I have seen


The hardest thing to get right

Problems with priority

  1. Priority inversion
  2. One resource, many clients
  3. Tasks try to do too much


  1. Too many tasks

Layered abstraction are costly

e.g. Notifier -> SerialServer -> InputAccumulater -> Parser -> TrackServer


  1. Too much terminal output interferes with train controller communication
  2. Requests to poll the sensors get backed up in the serial server, or whoever provides output buffering.


  1. Turn on optimization, but be careful
  2. Turn on caches

Size & align calibration tables by size & alignment of cache lines

I think that this is stretching it.

Return to: