Service: C/C++ Tipps:

'Critical Section' und Variablenzugriff aus unterschiedlichen Threads


Stellen Sie sich das typische Szenario einer Warteschlange in einem beliebigen Quellcode vor:

Ein Thread (der 'Schreiber') in Ihrer Anwendung empfängt Informationen und steckt sie in eine Warteschlange (Queue).

Ein anderer Thread (der 'Leser') arbeitet die Warteschlange ab, d.h: er sieht zyklisch in der Warteschlange nach, ob Informationen zur Weiterverarbeitung darin enthalten sind, entnimmt sie ggf. und verarbeitet sie weiter.

Der kritische Punkt in diesem Zusammenhang: Es muss sichergestellt sein, dass Schreiber und Leser niemals zum gleichen Zeitpunkt auf die Warteschlange zugreifen. Denn sollte das jemals geschehen, ist mit an Sicherheit grenzender Wahrscheinlichkeit der Arbeitsspeicher der Warteschlange beschädigt. In diesem Zusammenhang wird meist von 'Lock'oder 'Locking'-Mechanismen gesprochen.

Ein naheliegender Lösungsansatz besteht darin, einen 'bool' zu definieren. Bevor Schreiber oder Leser auf die Warteschlange zugreifen prüfen sie, ob dieser bool gesetzt (True) ist. Sobald der bool nicht mehr True ist, wird er gesetzt, auf die Warteschlange zugegriffen und anschließend zurückgesetzt. Z.B. so:

// Der Schreiber
    while (mbQueueInAccess);
    mbQueueInAccess = true;
    mQueue.push("[Eingehende Nachricht]");
    mbQueueInAccess = false;

// Irgendwo anders: Der Leser
    while (mbQueueInAccess);
    mbQueueInAccess = true;
    while (mQueue.empty())
    {
        WorkWithMessage(mQueue.front());
        mQueue.pop();
    }
    mbQueueInAccess = false;

Damit ist das Problem jedoch nicht gelöst sondern nur verschoben, denn nun besteht zwar keine Gefahr mehr für den Speicherbereich der Warteschlange, dafür aber für den der Kontrollvariablen. Es kann nicht sichergestellt werden, dass Schreiber und Leser quasi zur selben Zeit auf den Speicher der Kontrollvariablen zugreifen.


'Critical Section': die einfache und zuverlässige Lösung

Vorbereitungs- und Ensorgungsarbeiten

// Critical Section definieren
    CRITICAL_SECTION csQueueInAccess;
// Critical Section initialisieren
// (z.B. im Konstruktur einer Klasse oder anwendungsweit)
    InitializeCriticalSection(&csQueueInAccess);

// Critical Section irgendwo und irgendwie nach Bedarf verwenden (siehe unten)
    // ....

// Wichtig Critical Section nach Gebrauch umweltgerecht entsorgen
    DeleteCriticalSection(&csQueueInAccess);

Jetzt kann der Zugriff von Schreiber und Leser zuverlässig verwaltet werden:

// Der Schreiber
    EnterCriticalSection(&csQueueInAccess);
    mQueue.push("[Eingehende Nachricht]");
    LeaveCriticalSection(&csQueueInAccess);

// Irgendwo anders: Der Leser
    EnterCriticalSection(&csQueueInAccess);
    while (!mQueue.empty())
    {
        WorkWithMessage(mQueue.front());
        mQueue.pop();
    }
    LeaveCriticalSection(&csQueueInAccess);

Was hier auffällt und evtl. Verwirrung stiftet: Es existiert nichts in der Art von

    while (csQueueInAccess.Locked);

Das ist jedoch nicht erforderlich. Der Aufruf von EnterCriticalSection() sorgt automatisch dafür, dass - wenn erforderlich - gewartet wird.

Was ebenfalls auffällt - wenn man von der notwendigen Definition, Initialisierung und Deinitialisierung für das 'Critical Section' Objekt absieht: der eigentliche Code für das Schreiben in die Warteschlange und das Lesen aus ihr ist etwas übersichtlicher geworden, was daher kommt, dass die Endlosschleifen entfallen sind.


Konkretes Anwendungsbeispiel

Im Rahmen meines Projekts Benutzeroberfläche zur Einrichtung und Steuerung von Pipeline-Inspektions-Tools habe ich diesen Mechanismus eingesetzt, um eingehende CAN-Bus-Nachrichten zu verwalten.

Dabei bestand ein Ziel darin, eingehende CAN-Nachrichten zunächst sofort aus dem Nachrichtenpuffer der CAN-Hardware zu entnehmen, damit durch die notwendige Weiterverarbeitung der CAN-Nachrichten der Hardwarepuffer nicht blockiert wird.

Eine Nebenbedingung bestand darin, dass die CAN-Nachrichten von unterschiedlichen Typen physikalischer Baugruppen an die Bedieneroberfläche gesendet werden können und dementsprechend nach Baugruppentypen vorsortiert sind.

Die Implementierung erfolgte in Rahmen einer ATL-COM-Klasse. Die Lösung sieht (abgekürzt) wie folgt aus:

// Deklarationen:
    std::queue mquCanMsgSc;   // System Controller
    std::queue mquCanMsgPc;   // Power Controller
    std::queue mquCanMsgRpc;  // Regulated Power Controller
    std::queue mquCanMsgMsc;  // Mass Storage Controller
    std::queue mquCanMsgFe;   // Front End
    CRITICAL_SECTION mcsSc;
    CRITICAL_SECTION mcsPc;
    CRITICAL_SECTION mcsRpc;
    CRITICAL_SECTION mcsMsc;
    CRITICAL_SECTION mcsFe;

// Initialisierung in Konstruktur:
    InitializeCriticalSection(&mcsSc);
    // Das ganze fünf weiter Male für die jeweiligen Baugruppentypen
    // ...

// Deinitialisierung in Dekonstruktor:
    DeleteCriticalSection(&mcsSc);
    // Das ganze fünf weiter Male für die jeweiligen Baugruppentypen
    // ...

// Eingehende CAN-Nachricht in Warteschlange stecken:
// Von dieser Routine existieren Varianten für jeden Baugruppentyp
void __stdcall CCanCommunication::CanMsgProcSc(TYPE_CAN_MSG* pMsg)
{
// Die Nachricht aus dem erhaltenen Zeiger in ein lokales Objekt kopieren
    CanMsg CM; CopyMemory(&CM, pMsg, sizeof(CanMsg));

// Die Nachricht in die Queue stecken
    EnterCriticalSection(&gpCanCom->mcsSc);
    gpCanCom->mquCanMsgSc.push(CM);
    LeaveCriticalSection(&gpCanCom->mcsSc);
}

// CAN-Nachrichten zur Weiterverarbeitung aus den Warteschlangen entnehmen
LRESULT CCanCommunication::OnTimer(UINT uMsg, WPARAM wParam,
                                   LPARAM lParam, BOOL& bHandled)
{
    EnterCriticalSection(&gpCanCom->mcsSc);
    while (!mquCanMsgSc.empty())
    {
        CanMsg CM = mquCanMsgSc.front();
        mquCanMsgSc.pop();
        __raise CanMsgSc((long*)&CM);
    }
    LeaveCriticalSection(&gpCanCom->mcsSc);

// Das ganze fünf weiter Male für die jeweiligen Baugruppentypen
    // ...

    bHandled = TRUE;
    return 0;
}

Seitenanfang

Kontaktaufnahme- und Terminvereinbarung:

Bei Fragen und für Terminvereinbarungen erreichen Sie uns unter:

0 63 49 99 07 38

0 151 51 95 34 00

Oder nutzen Sie das Kontaktformular




Ihr Ansprechpartner:


Hier sollte das Fahnungsfoto zu sehen sein.

Ralf Kunsmann

Spezialist für VBA-Programmierung
(alle Office-Anwendungen)
Entwickler der
VBA-Extension-Tools