Unter einer Race Condition (Wettlaufsituation) versteht man den Fall, dass das kombinierte Resultat von zwei oder mehr Threads (bei denen es sich um RTOS Tasks oder main() plus eine ISR handeln kann) davon abhängig ist, in welcher Reihenfolge die Befehle der verschiedenen Tasks verzahnt werden.
Angenommen, es geht um zwei Threads, von denen einer regelmäßig eine globale Variable inkrementiert (g_counter += 1;), während der andere Thread diese Variable gelegentlich zurücksetzt (g_counter = 0;). Zu einer Race Condition kommt es hier, wenn die Inkrementierung nicht zwingend in einer nicht weiter unterteilbaren Einheit (d. h. in einem Befehlszyklus) erfolgt. Eine Kollision zwischen den beiden Aktualisierungen der Zählervariable kommt vielleicht selten oder nie vor. Sollte sie aber auftreten, unterbleibt das Zurücksetzen des Zählers im Speicher, sodass sein Wert verfälscht ist. Für das System kann dies ernste Konsequenzen haben, auch wenn sich diese möglicherweise erst einige Zeit nach der tatsächlichen Kollision manifestieren.
Abhilfe: Race Conditions lassen sich unterbinden, indem man die kritischen Codeabschnitte, die in einem Stück verarbeitet werden müssen, mit geeigneten Funktionen zur Preemption-Einschränkung einrahmt. Zur Vermeidung einer Race Condition im Zusammenhang mit einer ISR muss mindestens ein Interrupt-Signal für die Dauer des kritischen Abschnitts des anderen Codes gesperrt werden. Geht es dagegen um eine Wettlaufsituation zwischen zwei RTOS Tasks, muss speziell für das geteilte Objekt ein Mutex eingerichtet werden, den jede Task vor dem Eintritt in den kritischen Abschnitt abfragen muss. Verlassen Sie sich nicht auf die Fähigkeit bestimmter CPUs, die Unteilbarkeit bestimmter Codeabschnitte zu gewährleisten. Diese nämlich bieten nur so lange Schutz, bis Sie auf einen anderen Compiler oder eine andere CPU wechseln.
Geteilte Daten und das zufällige Timing von Preemptions sind die Ursachen für Race Conditions. Da diese Fehler jedoch nicht immer auftreten, lassen sie sich unter Umständen nur schwierig aufspüren. Es ist deshalb wichtig, höchst wachsam zu sein, was den Schutz aller geteilten Objekte betrifft.
Achten Sie außerdem darauf, alle potenziell geteilten Objekte – also globale Variablen, Heap-Objekte, Peripherie-Register und Zeiger – einheitlich zu benennen, damit das Risiko jedem, der den Code künftig liest, sofort deutlich wird. Der Embedded C Coding Standard der Barr Group empfiehlt beispielsweise die Verwendung des Präfixes g_ für diesen Zweck.