- Die Fäden in Windows Sie teilen sich den Speicher innerhalb desselben Prozesses, aber jeder hat seinen eigenen Stack und Kontext, was den Schutz des Zugriffs auf globale Daten erfordert.
- CreateThread, WaitForSingleObject, Mutex und CRITICAL_SECTION sind die grundlegenden Bausteine zum Erstellen, Warten auf und Synchronisieren von Threads in C mit der Win32-API.
- Durch die Übergabe von Parametern über LPVOID und die Verwendung gemeinsam genutzter Strukturen können mehrere Threads denselben Code wiederverwenden, während sie mit unterschiedlichen Daten arbeiten.
- Muster wie gemeinsam genutzte Zähler und Produzenten-Konsumenten-Muster zeigen die Notwendigkeit kritischer Abschnitte und Bedingungsvariablen auf, um Race Conditions zu vermeiden.

Wenn Sie unter Windows in C programmieren, werden Sie früher oder später Folgendes benötigen: mehrere Aufgaben gleichzeitig ausführenDie Verarbeitung von Schnittstellenereignissen, die Datenverarbeitung im Hintergrund, die Aktualisierung der Konsole usw. – hier kommen Threads ins Spiel. Obwohl wir unter GNU/Linux üblicherweise … pthread Und bei POSIX, unter Windows, ändert sich die Geschichte: Hier ist der Star der Win32-API Und wenn wir von „reinem und einfachem“ C sprechen, meinen wir Funktionen wie CreateThread, WaitForSingleObjectMutexe, kritische Abschnitte und Bedingungsvariablen.
In diesem Leitfaden werden Sie, ruhig und ohne Umschweife, sehen, Wie man Threads in C für Windows erstellt, verwaltet und synchronisiertWir beginnen mit der Definition eines Threads, fahren mit grundlegenden Beispielen für die Erstellung, die Parameterübergabe und die Rückgabe von Ergebnissen fort und schließen mit der Auseinandersetzung mit kritischen Abschnitten, Mutexen und einem Klassiker der Nebenläufigkeit ab: dem Produzent-Konsument-Muster. CRITICAL_SECTION y CONDITION_VARIABLEDies alles geschieht unter Verwendung der WinAPI und unter Berücksichtigung der Besonderheiten von Windows im Vergleich zu anderen Systemen.
Was ist ein Thread in Windows und wie unterscheidet er sich von einem Prozess?
Ein Faden ist, einfach ausgedrückt, die kleinste Ausführungseinheit die der Scheduler des Betriebssystems auf der CPU ausführt. Wenn Windows Prozessorzeit zuweist, geschieht dies nicht zwischen einzelnen Prozessen, sondern zwischen verschiedenen Prozessen. DrahtlosJeder Prozess kann wiederum einen oder mehrere aktive Threads haben.
Unter Windows werden Threads desselben Prozesses denselben Adressraum teilenDer gemeinsam genutzte Speicher umfasst ausführbaren Code, globale Daten, dynamischen Speicher (Heap) und die meisten geöffneten Ressourcen (Dateihandles, Sockets, Synchronisierungsobjekte usw.). Jeder Thread verfügt über einen eigenen Stack und Registerkontext, aber alle teilen sich denselben Speicher.
Ein Prozess hingegen ist ein isolierte Instanz eines Programms: hat seinen eigenen Bereich virtueller SpeicherDie Handle-Tabelle, die Thread-Konfiguration usw. – all das ist wichtig. Die Erstellung von Prozessen ist deutlich aufwendiger als die von Threads, da das System die gesamte isolierte Umgebung einrichten muss. Bei der Thread-Erstellung wird ein Großteil der Prozessumgebung wiederverwendet.
Auf Mehrkernrechnern kann Windows ausführen mehrere parallele DrähteEin Thread pro logischem Kern. Bei einem Prozessor mit vier Kernen können also vier Threads gleichzeitig ausgeführt werden. Die übrigen, ausführungsbereiten Threads erhalten über Kontextwechsel kleine Zeitabschnitte der CPU.
Wozu werden Threads in C-Anwendungen unter Windows verwendet?
Die Verwendung von Threads ermöglicht uns hauptsächlich zwei sehr interessante Dinge: zum einen acelerar die zeit der Ausführung die Verteilung der Arbeit auf mehrere Zentren und andererseits damit unsere Anwendung mehrere Dinge gleichzeitig erledigt ohne einzufrieren. Beispielsweise kann eine Konsolenanwendung weiterhin auf Tastatureingaben reagieren, während ein anderer Thread die Uhrzeit in einer Ecke des Bildschirms aktualisiert.
Im Kontext von Windows-Konsolenspielen oder -Tools sind Threads sehr nützlich für separate Spiellogik, Benutzereingaben und HintergrundaufgabenEin Thread kann permanent den Tastaturstatus überprüfen, ein anderer Animationen oder Farben auf dem Bildschirm aktualisieren und der Hauptthread die Hauptspielschleife verwalten.
Auch in anspruchsvolleren Desktop- oder Serverprogrammen werden häufig Threads verwendet. Anfragen parallel bearbeiten, Hintergrundprozesse durchführen, während die Benutzeroberfläche flüssig bleibt, oder E/A-Wartezeiten nutzen, um die CPU mit anderen Aufgaben zu beschäftigen.
Windows-Threadmodell: HANDLE, Eingabefunktion und zugehörige Ressourcen
Wenn Sie in Windows mithilfe der klassischen API einen Thread erstellen, gibt das System einen zurück. GRIFFDas heißt, ein „Handle“ oder ein undurchsichtiges Objekt, mit dem man auf den Thread warten, ihn abfragen oder ihn schließen kann; Werkzeuge wie z. B. Prozess-Hacker Sie ermöglichen es Ihnen, diese Threads zu untersuchen. Bei diesem Handle handelt es sich nicht um den Thread selbst, sondern um eine Referenz auf das vom Betriebssystem verwaltete Thread-Objekt.
Die vom Thread ausgeführte Funktion muss einer bestimmten Signatur entsprechen. In der Windows-API wird üblicherweise folgende Standardform verwendet:
DWORD WINAPI ThreadFunc(LPVOID arg);
Dass LPVOID arg Es handelt sich um einen generischen Zeiger, der Ihnen Folgendes ermöglicht: Übergeben Sie beliebige Daten als Parameter Innerhalb des Threads wandeln Sie diesen Zeiger in den entsprechenden Typ um, von einer Ganzzahl oder einer Struktur in eine komplexe Struktur mit mehreren Feldern.
Wenn der Thread endet, gibt die Funktion einen Wert zurück. DWORD mit dem Exit-Code des Threads. Vom Hauptthread aus können Sie diesen Wert später mithilfe der Funktion abrufen. GetExitCodeThread solange der Griff geöffnet bleibt.
Intern reserviert Windows für jeden Thread. ihren eigenen Stapel (Standardmäßig beträgt die Größe in der Regel etwa 1 MB, kann aber angepasst werden in CreateThreadund interne Strukturen wie den TEB (Thread Environment Block). Darüber hinaus verwaltet er alle für Kontextwechsel notwendigen Informationen: Register, Befehlszeiger, FPU-Zustand usw.
Kosten für Thread-Erstellung und Kontextwechsel in Windows
Das Erstellen von Threads ist ressourcenschonender als das Erstellen von Prozessen, aber nicht kostenlos. Jedes Mal, wenn Sie aufrufen CreateThreadWindows muss Stack zuweisen, TEB erstellen, Thread im Scheduler registrieren und gibt ein gültiges HANDLE zurück. Wenn Sie Tausende von kurzlebigen Threads benötigen, wird dieser Mehraufwand spürbar, und es ist sinnvoll, Thread-Pools in Betracht zu ziehen.
Hinzu kommen die Kosten für KontextänderungenJedes Mal, wenn der Scheduler die Ausführung von einem Thread an einen anderen übergibt, muss er die Register des ausgehenden Threads sichern und die des eingehenden Threads wiederherstellen, die Stacks aktualisieren usw. Da viele Threads bereit sind, verbringt das System mehr Zeit mit dem Wechseln von Kontexten und dem Verschieben von Daten im Cache als mit der Ausführung von nützlichem Code.
Wenn Sie die Standard-C-Bibliothek von Microsoft verwenden, sollten Sie wissen, dass die CRT-API Funktionen wie die folgenden bietet: _beginthreadexdie die pro Thread verwendete Laufzeitbibliothek korrekt initialisieren. Direkte Verwendung von CreateThread in Threads, die CRT-Funktionen aufrufen (zum Beispiel printf, malloc) können bei unsachgemäßer Handhabung zu Leckagen oder ungewöhnlichem Verhalten führen; außerdem können Techniken wie DLL-Injection Sie greifen üblicherweise auf von der API erzeugte Remote-Threads zurück.
Threads in C unter Windows erstellen: CreateThread Schritt für Schritt
Die Kernfunktion zum Starten von Threads in C mit WinAPI ist CreateThreadBeim Aufruf beginnt der Thread nahezu sofort mit der Ausführung (sofern nicht anders durch Flags angegeben). Schauen wir uns die wichtigsten Parameter an, die Sie täglich beherrschen sollten:
- lpThreadAttributesOptionale Sicherheitsstruktur. In den meisten Bildungs- oder Desktop-Szenarien wird sie weitergegeben.
NULLund es wird ignoriert. - dwStackSize: Anfangsstapelgröße. Wenn Sie 0 übergeben, Windows verwendet die Standardgröße In der ausführbaren Datei definiert.
- lpStartAddress: Adresse der Thread-Eingabefunktion, zum Beispiel
ThreadFunc. - lpParameter: Zeiger auf die Daten, die Sie an den Thread übergeben möchten. Er wird empfangen als
LPVOIDin der Funktion. - dwCreationFlagsDieses Flag steuert die Thread-Erstellung. Bei 0 startet der Thread sofort. Mit anderen Flags lassen sich Threads erstellen, die zunächst angehalten werden.
- lpThreadId: Zeiger auf ein
DWORDwo Windows schreiben wird numerischer Thread-IdentifikatorEs ist nützlich zum Debuggen oder für bestimmte spezifische Operationen.
Die Funktion gibt einen Wert zurück GRIFF Zum Thema. Wenn der Wert von folgendem abweicht: NULLEs wurde korrekt erstellt und der Thread-Funktionscode kann nun parallel zum Hauptthread ausgeführt werden.
Um den Hauptthread auf das Beenden eines Nebenthreads warten zu lassen, ist es sehr praktisch, Folgendes zu verwenden: WaitForSingleObjectMan übergibt den Thread-Handle und eine maximale Wartezeit. Wenn man die Konstante verwendet INFINITEDer Hauptthread bleibt blockiert, bis der sekundäre Thread beendet ist:
WaitForSingleObject(threadHandle, INFINITE);
Typischerweise startet der Hauptthread einen oder mehrere andere Threads, erledigt seine eigene Arbeit und beendet den Prozess bzw. verwendet die Ergebnisse dieser Threads erst kurz vor dessen Abschluss. Bitte warten Sie, bis der Vorgang abgeschlossen ist. mit dieser Funktion.
Grundlegende Steuerung der Ausführungsreihenfolge: Wartezeiten und Prüfungen
Da der Windows-Scheduler Threads nahezu jederzeit unterbrechen und fortsetzen kann, ist es manchmal praktisch, ihn einzuführen. künstliches Warten mit Sleep Nur zu Bildungszwecken. Wenn beispielsweise ein sekundärer Thread nur einen einzigen Befehl ausführt. printf Was sofort abgeschlossen wird, mag den Anschein erwecken, als würde die Wartereihenfolge missachtet, da die Ausgänge sehr nah beieinander liegen.
Hinzufügen eines Aufrufs Sleep(ms) Innerhalb des sekundären Threads erzwingen wir eine sichtbare Pause, die deutlich zeigt, dass der Hauptthread tatsächlich auf die Beendigung des sekundären Threads wartet, wenn wir ihn verwenden. WaitForSingleObjectDas ist sehr nützlich, wenn man lernt und möchte Man sieht die Verflechtung der Botschaften Multithread-Konsole.
Parameter an einen Thread übergeben und Ergebnisse zurückgeben
Einer der Schlüssel zu Programmierung mit Threads ist wie man Eingabedaten übergibt und wie man Ergebnisse abruftMit der klassischen Signatur DWORD WINAPI ThreadFunc(LPVOID data) Dies geschieht stets über den generischen Parameter. LPVOID.
Stellen Sie sich beispielsweise vor, Sie möchten einen Thread starten, der zwei ganze Zahlen addiert. Vom Hauptthread aus können Sie Folgendes tun: Speicher für ein Array von Ganzzahlen reservieren Bei zwei Positionen werden die Operanden dorthin kopiert und die Array-Adresse an übergeben. CreateThread wirft einen LPVOIDInnerhalb des Threads führen Sie die umgekehrte Typumwandlung durch. int* Und nun haben Sie Zugriff auf die Zahlen.
zu ein Ergebnis zurückgeben Innerhalb des Threads gibt es mehrere Möglichkeiten. Eine davon ist die Verwendung einer geschützten globalen Variable, falls weitere Threads beteiligt sind, was jedoch nicht besonders elegant ist. Eine andere, deutlich elegantere Option ist die Verwendung von dynamischem Speicher oder gemeinsam genutzten Strukturen.
- Sie reservieren ein Array oder eine Struktur mit Platz für Operanden und Ergebnis.
- Der Thread liest die Operanden, berechnet die Summe und schreibt das Ergebnis in denselben Speicherblock.
- Der Hauptfaden, sobald
WaitForSingleObjecthat bestätigt, dass der Thread beendet ist. Lesen Sie das Ergebnis diese Erinnerung.
Denken Sie immer daran Alle globalen Prozessvariablen sind für alle Threads sichtbar.Dies erleichtert zwar den Informationsaustausch, öffnet aber auch die Tür für eine Vielzahl von Karrierebedingungen, wenn der Zugang nicht angemessen geschützt wird.
Ermitteln Sie die von Windows einem Thread zugewiesene ID.
Zusätzlich zum HANDLE, das zurückgegeben wird CreateThreadWindows weist eine numerische Kennung (A DWORD) an jeden Thread. Wenn Sie an dieser ID interessiert sind, müssen Sie lediglich die Adresse eines DWORD im sechsten Parameter von CreateThread.
Zum Beispiel deklarieren Sie ein DWORD threadId;und beim Anrufen CreateThread Du gibst es ihm weiter. &threadIdWenn die Funktion erfolgreich zurückkehrt, enthält diese Variable die Thread-ID. Diese Information ist nützlich für Debugging und Protokollierung. Protokolle oder für einige fortgeschrittene API-Funktionen, die mit IDs anstelle von Handles arbeiten.
Mehrere Threads mit derselben Funktion: Code wiederverwenden
Einer der Vorteile des Gewindemodells ist, dass Dieselbe Eingabefunktion kann für viele verschiedene Threads verwendet werden.Der Clou ist, dass jeder Thread einen anderen Datenblock als Parameter erhält, sodass alle Threads zwar denselben Code ausführen, aber mit ihren eigenen Daten arbeiten.
Zum Beispiel können Sie definieren Sie eine Struktur mit den Informationen, die jeder Thread benötigt Um die Zeit an einer anderen Position in der Konsole anzuzeigen: X- und Y-Koordinaten sowie ein Exit-Flag. Im Hauptthread erstellen Sie zwei Instanzen dieser Struktur mit unterschiedlichen Koordinaten und starten zwei Threads, denen Sie die Adresse jeder Struktur übergeben. CreateThread.
Im Rahmen der Funktion des Gewindegießens LPVOID zu einem Zeiger auf Ihre Struktur, und von dort aus jeder Thread Es verwendet seine eigenen Koordinaten und seine eigene Ausgangsflagge.Der Code ist exakt derselbe, aber das Verhalten ist aufgrund dieser benutzerdefinierten Daten für jeden Thread unterschiedlich.
Mit einer einzigen Funktion können Sie also Die Uhrzeit gleichzeitig an mehreren Stellen auf dem Bildschirm anzeigenJeder Thread wird von einem anderen Thread gesteuert, der auf die Änderung der Sekunde wartet und die Zeit mit GetLocalTime und den Text mit seinen eigenen Koordinaten zu aktualisieren.
Synchronisation, kritische Abschnitte und Rennbedingungen
Wenn mehrere Threads auf gemeinsam genutzte Ressourcen zugreifen, ist es zwingend erforderlich, darüber zu sprechen kritische Abschnitte und Synchronisierung. Ein kritischer Abschnitt ist jeder Codeabschnitt, in dem ein Thread Liest oder schreibt Daten, die gleichzeitig von anderen Threads verwendet werden können.wie beispielsweise eine globale Variable, eine verkettete Liste, ein Puffer oder sogar der Konsolencursor.
Wenn zwei Threads gleichzeitig eine gemeinsam genutzte Struktur verändern dürfen, kann das Ergebnis völlig unvorhersehbar sein. Wenn beispielsweise 300 Threads denselben globalen Zähler um 1 erhöhen, wäre das logische Ergebnis 300, aber dies ist nicht garantiert. gegenseitiger AusschlussEs können Werte wie 298, 295 oder andere auftreten. Mehrere Summen überschneiden sich.
Bei der Windows-Konsole besteht ein typisches Problem darin, dass Der Cursor ist einzigartigWenn ein Thread den Cursor an bestimmten Koordinaten platziert und dann unterbrochen wird, kann ein anderer Thread die Koordinaten ändern und Text schreiben. Setzt der erste Thread seine Ausführung fort und schreibt seinen eigenen Text, setzt er dies an der Stelle fort, an der der zweite Thread aufgehört hat, und nicht an der beabsichtigten Stelle. Die Folge: Text an falschen oder durcheinandergewürfelten Positionen.
Um diese Katastrophen zu vermeiden, werden Synchronisationsmechanismen eingesetzt, wie zum Beispiel Mutexe, kritische Abschnitte und Bedingungsvariablendie einen serialisierten Zugriff auf gemeinsam genutzte Ressourcen ermöglichen und sicherstellen, dass sich jeweils nur ein Thread im kritischen Abschnitt befindet.
Mutex in Windows: Gegenseitiger Ausschluss für einfache kritische Abschnitte
Un Mutex (vom englischen „mutual exclusion“) ist ein Synchronisationsobjekt, das nur jeweils einen Faden besitzenSolange ein Thread den Mutex gesperrt hält, muss jeder andere Thread, der versucht, ihn zu erwerben, warten.
Unter Windows wird ein Mutex erstellt mit CreateMutexDies gibt ein HANDLE-Objekt wie jedes andere Systemobjekt zurück. Dieser Mutex kann einen globalen Namen haben; wenn Sie ihn mit einem Namen erstellen, können ihn andere Threads und sogar andere Prozesse öffnen. OpenMutexDie Grundidee ist:
- Erstelle den Mutex einmalig zu Beginn des Programms.
- Vor dem Eintritt in einen kritischen Abschnitt Warte auf den Mutex mit
WaitForSingleObject. - Führe den Code aus, der auf die gemeinsam genutzte Ressource zugreift.
- Am Ende, Den Mutex freigeben mit
ReleaseMutex.
Wenn ein Thread die Wartephase für den Mutex erreicht und sich bereits ein anderer Thread im kritischen Abschnitt befindet, blockiert das System diesen, bis der Mutex freigegeben wird. Dadurch wird sichergestellt, dass der Code zwischen dem Erwerb und der Freigabe des Mutex effektiv ausgeführt wird. als ob es atomar wäre zu anderen Themen.
In Konsolenanwendungen ist es sehr üblich, den Bildschirmzugriff in einer Funktion zu kapseln, die Fordern Sie den Mutex an, verfolgen Sie den Text und geben Sie ihn wieder frei.Zum Beispiel eine Funktion TrazarTexto(x, y, texto) könnte:
- Öffne oder empfange den HANDLE des gemeinsam genutzten Mutex.
- Aufruf
WaitForSingleObjectum es zu erhalten. - Platzieren Sie den Cursor mit
SetConsoleCursorPositionund schreibe mitprintf. - Den Mutex freigeben mit
ReleaseMutexund schließen Sie gegebenenfalls Ihren eigenen HANDLE.
Dadurch wird sichergestellt, dass auch dann, wenn mehrere Threads in die Konsole schreiben, die vollständigen Schreibsequenzen Position und Text sind geschützt und werden nicht vermischt. Vergessen Sie nicht, nach Abschluss des Programms Folgendes zu beachten: Schließe den Mutex HANDLE mit CloseHandle Ressourcen freizusetzen.
Klassisches Beispiel: 300 Threads, die einen Zähler inkrementieren.
Ein sehr hilfreiches Beispiel zum Verständnis kritischer Abschnitte ist der Start Hunderte von Threads erhöhen einen globalen Zähler.Ohne Synchronisierung stimmt der endgültige Zähler selten mit der Anzahl der gestarteten Threads überein, da sich mehrere Summen überschneiden.
Wenn Sie eine globale Ganzzahlvariable deklarieren und 300 Threads erstellen, die jeweils 1 zu dieser Variable addieren und dann terminieren, werden Sie sehen, dass das Ergebnis beispielsweise 298 sein kann. Das zeigt, dass es Rennbedingungen: Zwei Threads lesen den alten Wert, erhöhen ihn jeweils unabhängig voneinander und schreiben denselben Wert zurück, wodurch eine Summe verloren geht.
Wenn Sie einen Mutex um die Inkrementoperation einführen, d. h. jeder Thread führt Folgendes aus: WaitForSingleObject, erhöht den Zähler und dann ReleaseMutexAlle Threads reihen sich in eine Warteschlange ein, um ihre Summe zu berechnen. Zu jedem Zeitpunkt führt nur ein Thread das entsprechende Fragment aus, und wenn das Programm beendet ist, steht der Zähler auf 300.
Dieses Muster, einen Mutex kurz vor dem Zugriff auf die gemeinsam genutzten Daten zu erwerben und ihn sofort nach Abschluss des Zugriffs wieder freizugeben, ist das Wesen des gut geschützte kritische Abschnitte und die Grundlage für sichere parallele Programmierung.
Produzenten und Konsumenten mit CRITICAL_SECTION und ConditionVariable
Wenn Sie etwas Komplexeres als einen einfachen Zähler benötigen, kommen traditionelle Parallelverarbeitungsmuster zum Einsatz, wie beispielsweise das folgende: Produzent-KonsumentUnter Windows gibt es neben Mutexen auch schlankere und spezifischere Mechanismen wie zum Beispiel CRITICAL_SECTION y CONDITION_VARIABLE, perfekt geeignet zur Koordination mehrerer Threads innerhalb desselben Prozesses.
Stellen Sie sich ein Szenario mit 150 Produzenten-Threads und 150 Konsumenten-Threads vor, die einen Puffer mit nur 10 Elementen gemeinsam nutzen. Jeder Produzent erzeugt Elemente für den Puffer und pausiert dabei zufällig zwischen 0 und 50 ms. Jeder Konsument liest die Elemente anschließend in seinem eigenen Tempo aus dem Puffer. Die Ausführungsreihenfolge der Threads ist nicht garantiert, aber der Puffer muss... Bitte nicht mit mehr als 10 Elementen füllen oder im leeren Zustand lesen..
Zur Lösung dieses Problems werden drei grundlegende Elemente verwendet:
- Eine
CRITICAL_SECTIONdas den Zugriff auf den Puffer und seine Indizes schützt. - Eine Bedingungsvariable für Produzentenum sie aufzuwecken, wenn freier Platz vorhanden ist.
- Eine Bedingungsvariable für Konsumentenum sie aufzuwecken, wenn Gegenstände verfügbar sind.
Das allgemeine Schema der Produzenten ist:
- Geben Sie den kritischen Abschnitt ein mit
EnterCriticalSection. - Solange der Puffer voll ist, wird der Thread mit der entsprechenden Bedingungsvariablen in den Ruhezustand versetzt: Aufruf
SleepConditionVariableCSdie Bedingung dem kritischen Abschnitt zuordnen. - Wenn genügend Platz vorhanden ist, wird das Element erzeugt, in den Puffer eingefügt und die Indizes werden aktualisiert.
- Verlassen Sie den kritischen Abschnitt mit
LeaveCriticalSectiony Zielgruppe dass es Elemente gibtWakeConditionVariableoWakeAllConditionVariable.
Konsumenten folgen einer spiegelbildlichen Logik:
- Sie treten in den kritischen Abschnitt ein.
- Solange der Puffer leer ist, warten sie im Verbraucherzustand.
- Wenn Elemente vorhanden sind, wird eines verbraucht und der Pufferzustand aktualisiert.
- Sie lassen den kritischen Abschnitt außer Acht und Sie wecken die Produzenten auf. um anzuzeigen, dass Platz frei geworden ist.
Dieses Muster stellt sicher, dass kein Produzenten-Thread in den Puffer schreibt, wenn dieser voll ist (er wird blockiert, bis ein Konsument Speicherplatz freigibt) und dass kein Konsument versucht, aus dem leeren Puffer zu lesen (er bleibt im Leerlauf, bis ein neues Element verfügbar ist). Darüber hinaus wird all dies erreicht durch geschützter Zugriff auf den Puffer dank der CRITICAL_SECTION.
Threads und Entwicklungsumgebung: Hinweise zu Visual Studio und C#
Obwohl wir uns hier auf C und WinAPI konzentrieren, findet man sehr häufig Beispiele im Windows-Ökosystem. Visual C# und .NET Verwendung des Namensraums System.ThreadingDie Grundidee ist dieselbe: Es werden Threads erstellt, eine Funktion gestartet, die parallel ausgeführt wird, und eine Fortschrittsanzeige oder ein grafisches Steuerelement aktualisiert, während der Hauptthread weiterhin reagiert.
In C# ist es beispielsweise typisch, eine Windows Forms-Anwendung mit einem sekundärer Thread, der eine Fortschrittsanzeige manipuliert oder ändert die Farben, während der Hauptthread auf Schaltflächenereignisse reagiert. Die Logik ist ähnlich: Ein Thread führt eine Schleife mit Thread.Sleep Um eine CPU-Überlastung zu vermeiden, ändert ein Thread Werte, während ein anderer Nachrichten sendet oder Klicks verarbeitet. Auch in diesem Fall muss die Synchronisierung des Zugriffs auf die UI-Steuerelemente sorgfältig verwaltet werden, das Prinzip, dass sich Threads nicht gegenseitig blockieren dürfen, bleibt jedoch unverändert.
Wichtig ist, dass beide mit Thread in C# wie mit CreateThread o _beginthreadex In C gelten die gleichen Regeln wie Parallelverarbeitung, kritische Abschnitte und gemeinsam genutzte RessourcenWechseln Sie einfach die Bibliothek, mit der Sie arbeiten.
Im Großen und Ganzen ermöglicht das Windows-Threading-Modell die Erstellung eines einfachen Threads, der Meldungen ausgibt mit Sleep Selbst beim Aufbau komplexer Architekturen mit Dutzenden von Produzenten- und Konsumententhreads, die mit Mutexen, kritischen Abschnitten und Bedingungsvariablen koordiniert werden, kommt es darauf an, genau zu verstehen, was jeder Thread gemeinsam nutzt, wann synchronisiert werden muss und wie man die WinAPI-Funktionen bedenkenlos einsetzen kann, um die Erstellung, das Warten und die Beendigung jedes einzelnen Threads zu steuern.
Leidenschaftlicher Autor über die Welt der Bytes und der Technologie im Allgemeinen. Ich liebe es, mein Wissen durch Schreiben zu teilen, und genau das werde ich in diesem Blog tun und Ihnen die interessantesten Dinge über Gadgets, Software, Hardware, technologische Trends und mehr zeigen. Mein Ziel ist es, Ihnen dabei zu helfen, sich auf einfache und unterhaltsame Weise in der digitalen Welt zurechtzufinden.
