Share via


Multithreading: Verwenden der MFC-Synchronisierungsklassen

Die Synchronisierung des Zugriffs auf Ressourcen durch Threads ist ein häufiges Problem beim Schreiben von Multithreadanwendungen. Wenn zwei oder mehr Threads gleichzeitig auf dieselben Daten zugreifen, kann dies zu unerwünschten und unvorhersehbaren Ergebnissen führen. Es könnte z. B. vorkommen, dass der eine Thread den Inhalt einer Struktur aktualisiert, während der andere Thread gleichzeitig den Inhalt dieser Struktur liest. Es gibt keine Informationen darüber, welche Daten vom lesenden Thread in einem solchen Fall empfangen werden: die alten Daten, die neu geschriebenen Daten oder möglicherweise eine Kombination aus beidem. MFC enthält eine Reihe von Synchronisierungs- und Synchronisierungszugriffsklassen, die Sie bei der Lösung dieses Problems unterstützen. In diesem Thema werden die verfügbaren Klassen erläutert, und Sie erfahren, wie Sie diese in einer typischen Multithreadanwendung zum Erstellen von threadsicheren Klassen verwenden können.

Eine typische Multithreadanwendung enthält eine Klasse, die eine Ressource darstellt, die von Threads gemeinsam genutzt wird. Bei einer vorschriftsmäßig entwickelten, vollständig threadsicheren Klasse ist der Aufruf von Synchronisierungsfunktionen nicht erforderlich. Alles wird klassenintern geregelt, damit Sie sich auf die optimale Verwendung der Klasse konzentrieren können und nicht darauf, wie sie eventuell fehlerhaft werden könnte. Die beste Methode zum Erstellen einer vollständig threadsicheren Klasse ist die Einbindung der Synchronisierungsklasse in die Ressourcenklasse. Die Einbindung von Synchronisierungsklassen in eine gemeinsam genutzte Klasse ist ein einfacher Vorgang.

Als Beispiel wird eine Anwendung verwendet, in der eine verknüpfte Liste von Konten verwaltet wird. In dieser Anwendung können drei Konten in verschiedenen Fenstern untersucht werden, es kann jedoch nur jeweils ein Konto aktualisiert werden. Bei der Aktualisierung eines Kontos werden die aktualisierten Daten über das Netzwerk an ein Datenarchiv gesendet.

In dieser Beispielanwendung kommen alle drei Arten von Synchronisierungsklassen zum Einsatz. Da es bis zu drei Konten gleichzeitig untersucht werden kann, verwendet es CSemaphor , um den Zugriff auf drei Ansichtsobjekte einzuschränken. Beim Versuch, ein viertes Konto anzuzeigen, wartet die Anwendung entweder, bis eines der ersten drei Fenster geschlossen wird, oder der Versuch schlägt fehl. Wenn ein Konto aktualisiert wird, verwendet die Anwendung CCriticalSection , um sicherzustellen, dass jeweils nur ein Konto aktualisiert wird. Nachdem das Update erfolgreich war, signalisiert es CEvent, wodurch ein Thread loslässt, der darauf wartet, dass das Ereignis signalisiert wird. Dieser Thread sendet die neuen Daten an das Datenarchiv.

Entwerfen einer Thread-Tresor-Klasse

Um eine Klasse vollständig threadsicher zu gestalten, fügen Sie zunächst den gemeinsam genutzten Klassen die entsprechende Synchronisierungsklasse als Datenmember hinzu. Im vorherigen Kontoverwaltungsbeispiel würde der Ansichtsklasse ein CSemaphore Datenmememm hinzugefügt, ein CCriticalSection Datenmemmemm zur Klasse "Linked-List" hinzugefügt und ein CEvent Datenmememm der Datenspeicherklasse hinzugefügt.

Fügen Sie anschließend allen Memberfunktionen, die die Daten in der Klasse ändern bzw. auf eine gesteuerte Ressource zugreifen, Synchronisierungsaufrufe hinzu. In jeder Funktion sollten Sie entweder ein CSingleLock - oder CMultiLock-Objekt erstellen und die Funktion dieses Lock Objekts aufrufen. Wenn das Sperrobjekt außerhalb des Gültigkeitsbereichs liegt und zerstört wird, ruft der Destruktor des Objekts für Sie auf Unlock und gibt die Ressource frei. Selbstverständlich können Sie Unlock auch direkt aufrufen.

Wenn Sie auf diese Art eine threadsichere Klasse entwerfen, kann sie in einer Multithreadanwendung ebenso problemlos wie eine nicht threadsichere Klasse verwendet werden, jedoch mit größerer Sicherheit. Wenn sowohl das Synchronisierungsobjekt als auch das Synchronisierungszugriffsobjekt in die Klasse der Ressource eingeschlossen werden, können Sie sämtliche Vorteile einer vollständig threadsicheren Programmierung nutzen, ohne Synchronisierungscode verwalten zu müssen.

Im folgenden Codebeispiel wird diese Methode anhand des m_CritSection-Datenmembers (vom Typ CCriticalSection) demonstriert, das in der gemeinsam genutzten Ressourcenklasse deklariert wurde; außerdem wird ein CSingleLock-Objekt verwendet. Der Versuch der Synchronisierung der gemeinsam genutzten Ressource (die von CWinThread abgeleitet wurde) wird durch Erstellung eines CSingleLock-Objekts unter Verwendung der Adresse des m_CritSection-Objekts unternommen. Nachdem die Ressource erfolgreich gesperrt wurde, wird das gemeinsam genutzte Objekt bearbeitet. Nachdem der Bearbeitungsvorgang abgeschlossen ist, wird die Ressource durch Aufruf von Unlock entsperrt.

CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...

singleLock.Unlock();

Hinweis

Bei CCriticalSection steht im Gegensatz zu anderen MFC-Synchronisierungsklassen keine Option für eine zeitgesteuerte Sperranforderung zur Verfügung. Für die Zeit, die bis zur Freigabe eines Threads verstreichen kann, gibt es keinerlei Beschränkungen.

Der Nachteil dieses Ansatzes liegt darin, dass die Klasse etwas langsamer als die gleiche Klasse ohne hinzugefügte Synchronisierungsobjekte ist. Wenn das Objekt außerdem durch mehrere Threads gelöscht werden könnte, führt eine Einbindung nicht immer zum Erfolg. In dieser Situation ist es besser, separate Synchronisierungsobjekte zu verwalten.

Informationen zum Ermitteln der zu verwendenden Synchronisierungsklasse finden Sie unter Multithreading: Verwendung der Synchronisierungsklassen. Weitere Informationen zur Synchronisierung finden Sie unter "Synchronisierung " im Windows SDK. Weitere Informationen zur Multithreading-Unterstützung in MFC finden Sie unter Multithreading mit C++ und MFC.

Siehe auch

Multithreading mit C++ und MFC