Multi-task data access synchronization in the PLC

When the same data is accessed by multiple tasks, the tasks may access the same data simultaneously, depending on the task/real-time configuration. If the data is written by at least one of the tasks, the data may have an inconsistent state during or after a change. To prevent this, all concurrent accesses must be synchronized so that only one task at a time can access the shared data.

These concurrent accesses from several tasks that require synchronization include the following cases, for example:

  • Direct access to global or other non-temporary variables, for example using operators
  • Indirect access to global or other non-temporary variables, for example within functions, methods, or other POU calls (especially if a function block instance is globally instantiated)

To put it briefly: If the same data is accessed by several tasks and the data is written for at least one of these accesses, all read and write accesses must be synchronized. This applies irrespective of whether the tasks run on one or more CPU cores.

Inconsistencies and other risks due to unsecured data access

If concurrent accesses are not synchronized, there is a risk of inconsistent or invalid data records. Depending on how the data is used in the further course of the program, this can result in incorrect program behavior, undesired axis movement or even sudden program standstill. Depending on the controlled system, damage to equipment and workpieces may occur, or people's health and lives may be endangered.

To get a feeling for the necessity of access synchronization, you will find function tests with corresponding explanations in the sample programs for the MUTEX procedures.

Synchronization options

The following options are available for synchronizing accesses:

  • Mutex procedure (TestAndSet, FB_IecCriticalSection) for securing critical sections
    • The number of critical sections must always be kept as small as possible.
    • The critical sections must be kept short.
    • For comparatively short critical sections, the use of FB_IecCriticalSection is usually recommended.
    • For comparatively long critical sections, the use of TestAndSet is usually recommended.
  • Data exchange via the PLC process image
    • This variant is only possible and recommended if only one task has write access to the same data.
    • The possible data volume is limited due to necessary internal copy actions. For further information, please refer to the description under “Data exchange via the PLC process image”.
  • Data exchange via synchronized buffers
    • This variant is only possible if only one task has write access to the same data.
    • This is a user-specific implementation for which there are various options.
    • Access to the individual buffers must be secured, for example with TestAndSet().
    • The possible data volume is limited due to executed copy actions. For further information, please refer to the description under “Data exchange via synchronized buffers”.

Synchronization also in the case of atomic access

The necessity for synchronization normally also applies even if a single access to a variable (e.g. writing an integer) could be described as atomic, i.e. uninterruptible.

Because the property of the atomic access depends among other things on the processor architecture used, every access should be regarded as non-atomic for simplicity's sake and for safety.

It should also be noted that even supposedly safe accesses almost always turn out to be unsafe when considered more closely. This is explained below with the help of two example scenarios:

  • As a rule, more than one atomic access is necessary for the desired function expression (e.g. read, change, write). If such a multi-part function expression exists in several tasks, the simultaneous execution on several tasks can lead to a different result than the sequential execution of the expressions.
    • Example: A global counter variable (initialized with 0) is to be incremented by 1 (from two tasks) (nGlobal := nGlobal + 1;). If the incrementation is executed at the same time, 0 + 1 = 1 is calculated both times and the resulting value of the global variable is 1, although the value 2 was intended.
  • Several read accesses at different times within a task execution can deliver different variable values.
    • Example: A global variable is written from a task. Its value was previously 50 and is now written to 0 (nGlobal := 0;). In a different task context the value of the global variable is queried (IF nGlobal>10 AND nGlobal<20 THEN). The query consists of two read accesses. If the above write access takes place from the other task between these read accesses, then the condition is fulfilled, even though the global variable did not have a value of between 10 and 20 at any point in time.

Additional Notes

  • System operators such as ADD, SIZEOF or __NEW can be called by several tasks simultaneously. This statement only refers to the operator used. If data is accessed during the calls, which in turn is used by several tasks, these data accesses must be synchronized accordingly.
  • A function block instance that uses ADS internally may not be used in different tasks. If the instance is nevertheless called from another task, the error ADSERR_DEVICE_INVALIDCONTEXT=0x709=1801 is issued.
  • Due to the execution on the stack, the use of the STRING functions (Tc2_Standard library) in different tasks is not critical in TwinCAT 3 (in TwinCAT 2 the STRING functions are not safe by default for task changes).
  • Also note the possibilities of the compiler extension (documentation TE1200 TC3 PLC Static Analysis), which provides rules on the topic of “concurrency/competing accesses”. This should be seen as a tool to identify potential synchronization requirements.