Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

FlyingDragon

Frischling

  • »FlyingDragon« ist der Autor dieses Themas

Beiträge: 38

Wohnort: Sachsen

Beruf: Consultant / Software Architekt

  • Private Nachricht senden

1

05.11.2006, 00:35

Einstieg in Multithreading (unter Windows)

Einstieg in Multithreading
von Sven Burow

Einleitung
Herzlich willkommen in meinem Tutorial über das Erstellen von Programmen mit Threads. Ich möchte ihnen hier einen kleinen Einblick in die Welt von Windowsthreads geben und ihnen zeigen, wie man Threads für seine Win32-Programme nutzen kann.

Was sind Threads und wozu braucht man sie?
Um den ersten Teil der Frage zu beantworten möchte ich Wikipedia zitieren:

Zitat von »"Wikipedia"«

Ein Thread (auch Aktivitätsträger), in der deutschen Literatur vereinzelt auch als Faden bezeichnet, ist in der Informatik ein Ausführungsstrang beziehungsweise eine Ausführungsreihenfolge der Abarbeitung der Software.

http://de.wikipedia.org/wiki/Thread_%28Informatik%29

Jedes gestartetete Programm besteht mindestens aus einem Thread. Wenn Sie mir das nicht glauben, können Sie gern den Taskmanager starten und sich von allen Prozessen die Threadanzahl anzeigen lassen. Ein Prozess kann aber auch aus mehreren Threads bestehen. Jeder Thread erfüllt bei dem Programm eine bestimmte Teilaufgabe. Zum Beispiel kann ein Thread im Hintergrund die E-Mails abrufen, während man sich bereits empfangene E-Mails in aller Ruhe anschauen kann. Hätte man keine Threads, so müsste man erst alle E-Mails runterladen und könnte erst danach sich diese Anschauen.

Viele neue CPUs haben 2 Prozessorkerne. Ein Thread läuft immer genau auf einem Prozessorkern. Der Thread kann aber vom Betriebssystem auf den anderen Prozessorkern umgelagert werden. Um nun beide Prozessorkerne voll ausnutzen zu können, muss ein Programm über min. 2 Threads verfügen.

Los geht´s
Alles was man braucht um auf die Thread-API von Windows zugreifen zu können, ist die windows.h-Headerdatei einzubinden.

C-/C++-Quelltext

1
#include <windows.h>


Der erste Thread eines Prozesses führt den Code aus der Main aus. Um nun dem Thread zu sagen, welchen Code er ausführen soll, brauchen wir eine Funktion. Diese Funktion bekommt immer einen Zeiger übergeben. Beim erstellen des Threads können sie diesen Zeiger angeben. Diesen können Sie verwenden um Startwerte oder ähnliches zu übergeben. Die Thread-Prozedur hat einen Rückgabewert vom Typ DWORD (unsigned long). Diesen können Sie analog zum Rückgabewert der main verstehen. Sie können sogar mit einer Funktion aus der Thread-API den Rückgabewert eines beendeten Threads abfragen. Für weitere Informationen einfach in der MSDN schauen.

C-/C++-Quelltext

1
DWORD WINAPI ThreadProc(LPVOID lpParam)


Den Thread starten Sie mit Hilfe der Funktion CreateThread. Diese erwartet eine Reihe von zu übergebene Parameter. Der erste Parameter ist ein Zeiger auf eine Variable vom Typ SECURITY_ATTRIUTES. Diesem Parameter übergeben Sie einfach die Konstante NULL. Der zweite Parameter ist um einiges interessanter. Jeder Thread bekommt einen eigenen Stack. Angegeben wird hierbei die Größe des Stacks. Wenn Sie 0 übergeben, wird ein systemspezifischer Standardwert genommen. Einen Zeiger auf die Threadfunktion wird als dritter Parameter übergeben. Dem vierten Parameter können Sie den Wert 0 für sofortiges Starten des Threads oder die Konstante CREATE_SUSPENDED für ein Blockieren des Threads angeben. Sollten Sie einen Thread mit CREATE_SUSPENDED angelegt haben, können Sie ihn mit ResumeThread starten. Als fünften und letzen Parameter muss ein Zeiger auf eine Varialbe vom Typ DWORD für die ID des Threads übergeben werden. Die Funktion gibt bei erfolgreichen erstellen des Threads ein Handle auf den Thread zurück.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
);


Nachdem ein Thread beendet wurde, muss das Handle geschlossen werden. Dafür gibt es die Funktion CloseHandle.

C-/C++-Quelltext

1
2
3
BOOL CloseHandle(
  HANDLE hObject
);


Manchmal soll ein Thread auch einfach nur warten und nichts machen um zum Beispiel den anderen Threads und Prozessen etwas Zeit zu geben. Die gesuchte Funktion trägt den Namen Sleep. Wie der Name der Funktion sagt, legt sie einen Thread schlafen. Ihr wird die Wartezeit in Millisekunden übergeben. Übergibt man ihr den Wert 0, so wird erst den anderen Threads etwas Zeit gegen und danach sofort weitergemacht.

C-/C++-Quelltext

1
2
3
void Sleep(
  DWORD dwMilliseconds
);


Beispiel
Hier möchte ich Ihnen ein kleines Beispiel vorführen. Es macht nichts weiter als einen Thread zu starten, welcher dann von 5 bin 0 runterzählt und bei erreichen der 0 den Thread verlässt. Diese kleine Beispiel ist in C geschreiben und verwendet daher printf. Es sollte aber kein Problem sein diesen Quellcode auch für C++ umzustellen.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* threads_1.c */

#include <stdio.h>
#include <conio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    int i;

    printf("Ich bin ein Thread und werde mich in 5 Sekunden von selbst beenden\n");

    for(i=5; i>0; --i)
    {
        printf("Noch %d Sekunden\n", i);
        Sleep(1000);
    }

    printf("Und Tschuess\n");
}

int main()
{
    DWORD   threadId;
    HANDLE  threadHandle;

    int i=0;

    printf("Ich bin das Hauptprogramm\n");

    threadHandle = CreateThread(NULL, 0, ThreadProc, NULL, CREATE_SUSPENDED, &threadId);

    printf("Thread mit der ID=%d gestartet\n", threadId);

    getch();

    CloseHandle(threadHandle);

    return 0;
}


Kritsiche Bereiche
Wenn mehrere Threads laufen ist es meistens unvermeidlich, dass Sie auf die selben Resourcen zugreifen. Passiert das zur gleichen Zeit, kann es sein dass es knallt oder etwas sehr seltsames und unverhofftes passiert. Lassen Sie mich das an einem kleinen Beispiel erklären.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* threads_2a.c */

#include <stdio.h>
#include <conio.h>
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    int i;
    DWORD idThread;

    idThread = GetCurrentThreadId();

    for(i=0; i<10; ++i)
    {
        printf("%d: Hallo du wunderschoene Welt!\n", idThread);

        Sleep(1000);
    }

    return 0;
}

int main()
{
    DWORD   threadId[3], mainThreadId;
    HANDLE  threadHandle[3];
    int i=0;

    mainThreadId = GetCurrentThreadId();

    printf("%d: Ich bin das Hauptprogramm\n", mainThreadId);

    for(i=0; i<3; ++i)
    {
        threadHandle[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadId[i]);

        printf("Thread mit der ID=%d erstellt\n", threadId[i]);
    }

    getch();

    for(i=0; i<3; ++i)
        CloseHandle(threadHandle[i]);

    return 0;
}

Dieses Programm erstellt drei Threads die jeweils die gleiche Threadfunktion bekommen und daher alle das gleiche machen. Jeder Thread soll 10 mal den Text "Hallo du wunderschoene Welt!" in der Konsole ausgeben. Wann man das Programm nun compiliert und startet bekommt man mit etwas Glück eine sehr seltsame Ausgabe.

(Link)

Grund für diese seltsame Ausgabe ist der gleichzeitige Zugriff der Threads auf die Konsole.Während der eine Thread gerade ein Wort auf die Konsole ausgeben konnte fängt ein anderer Thread an mit der Ausgabe seines Textes an.

Abhilfe schaffen die sogenannten kritischen Bereiche oder engl. critical sections. Für kritsiche Bereiche stellt uns die Thread-API von Windows ein paar Funktionen zur Verfügung die ich Ihnen nun vorstellen werde.

Zu erst muss ein Objekt für den kritischer Bereich mit InitializeCriticalSection initializiert werden. Diese Funktion erwartet einen Zeiger auf eine Variable vom Typ CRITICAL_SECTION. Die Varialbe können Sie als globale definieren. Alternativ können sie auch den Zeiger als Paramter dem Thread übergeben. Es muss aber gewährleistet sein, dass alle Threads zugriff auf den Zeiger haben.

C-/C++-Quelltext

1
2
3
void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

Wichtig: CRITICAL_SECTION ist eine Struktur und wird von Windows verwaltet. Ändern Sie niemals einen Wert der Struktur!

Befindet sich nun ein Thread in einem kritischen Bereich im Programm, so können Sie mit der Funktion EnterCriticalSection den kritschen Bereich betreten. Befindet sich ein Thread in dem kritischen Bereich, so müssen alle anderen Threads die ebenfalls den kritschen Bereich betreten wollen warten. Windows versucht mit den Threads fair umzugehen. Daher kommen alle Threads mal dran. Achtung: Die Reihenfolge ist aber nicht festgelegt.

C-/C++-Quelltext

1
2
3
void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

Soll der kritische Bereich verlassen werden, so müssen Sie die Funktion LeaveCriticalSection aufrufen. Man sollte niemals vergessen einen kritischen Bereich zu verlassen, denn sonst hängt das Programm in einer Endlosschleife. Ich möchte Sie auch noch hinweisen, nur wirklich kritsche Bereiche zu definieren, die auch wirklich kritisch sind. Zum beispiel Sleep-Anweisungen haben darin nichts zu suchen.

C-/C++-Quelltext

1
2
3
void LeaveCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

Gibt es keinen Thread mehr, der den kritischen Bereich betreten kann, dann muss dieser gelöscht werden. Das kann zum Beispiel bei Programmende erfolgen. Hierfür rufen Sie die Funktion DeleteCriticalSection auf. Bitte beachten Sie, dass es wirklich ausgeschlossen ist, dass ein Thread in diesen kritischen Bereich kommt.

C-/C++-Quelltext

1
2
3
void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);


Und hier ein Lösungsvorschlag für das Ausgabeproblem.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* threads_2.c */

#include <stdio.h>
#include <conio.h>
#include <windows.h>

/* Ein globales Objekt  für den kritischen Bereich bei der Textausgabe */
CRITICAL_SECTION g_csPrint;

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    int i;
    DWORD idThread;

    idThread = GetCurrentThreadId();

    for(i=0; i<10; ++i)
    {
        /* den kritische Bereich betreten */
        EnterCriticalSection(&g_csPrint);

        printf("%d: Hallo du wunderschoene Welt!\n", idThread);

        /* den kritischen Bereich verlassen*/
        LeaveCriticalSection(&g_csPrint);

        Sleep(1000);
    }

    return 0;
}

int main()
{
    DWORD   threadId[3], mainThreadId;
    HANDLE  threadHandle[3];
    int i=0;

    /* Kritschen Bereich initialisieren */
    InitializeCriticalSection(&g_csPrint);

    mainThreadId = GetCurrentThreadId();

    printf("%d: Ich bin das Hauptprogramm\n", mainThreadId);

    for(i=0; i<3; ++i)
    {
        threadHandle[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadId[i]);

        printf("Thread mit der ID=%d erstellt\n", threadId[i]);
    }

    getch();

    for(i=0; i<3; ++i)
        CloseHandle(threadHandle[i]);

    /* kritischen Bereich löschen*/
    DeleteCriticalSection(&g_csPrint);

    return 0;
}


Schlusswort
Threads können manche Dinge sehr vereinfachen aber auch sehr verkomplizieren. Sollte interesse an eine Fortsetzung oder eine Erweiterung dieses Tutorials bestehen, dann meldet euch einfach. Außerdem könnt ihr noch Fragen, Kommentare und Kritik posten.
Achtung! Das Lesen dieses Beitrags kann Ihnen und den Menschen in ihrer Umgebung erheblichen Schaden zufügen.
___________
Mein Leben, Freunde und die Spieleentwicklung

Das Gurke

Community-Fossil

Beiträge: 1 996

Wohnort: Pinneberg

Beruf: Schüler

  • Private Nachricht senden

2

05.11.2006, 10:48

Super geschrieben! Erst eine wunderbare Einführung die dann von einem kurzen Stückchen Code untermauert wird, so muss das doch sein =)

Hmm, Weiterführung ... Ich fände Threads in C++ spannend, also unter Verwendung von Objektorientierter Programmierung. Ich weiss das es dafür Librarys gibt, aber ich hab da noch keine gefunden die mir so richtig geholfen hat. Ich fänds von daher wirklich toll was zu Threads und OOP zu lesen, ob mit Lib oder ohne ist mir dabei schnuppe (ehrlich gesagt keine Ahnung wie komplex diese Libs sind).

riCo

Treue Seele

Beiträge: 165

Beruf: Student

  • Private Nachricht senden

3

05.11.2006, 11:31

Ich habe irgendwo gelesen, dass wenn man mit Threads arbeitet die Anwendung mehr CPU-Zeit und eine höhere Priorität zugeordnet bekommt. Ist das richtig?

Übrigens wäre es noch ein interessanten Kapitel, wenn du auf "critical section" eingehen könntest.
Wir leben alle unter dem Sternenhimmel, aber wir haben nicht alle den gleichen Horizont.

T-VIRUS

Alter Hase

Beiträge: 548

Wohnort: Göttingen(West)/Nordhausen(Ost)

Beruf: Schüler

  • Private Nachricht senden

4

05.11.2006, 12:27

Sieht interessant aus ;)
Werds mir mal genauer ansehen ;)
Meine Blog:)

Wer Bugs im Text findet kann sie melden, fix erscheint irgendwann :D

MFG T-VIRUS

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

5

05.11.2006, 12:44

Nur so nebenbei. Das ist sozusagen nur der Grundstein für Multithreading.
@riCo du kannst jedem Thread eine Priorität zuweisen. Aber natürlich ist es auch so, dass wenn eine Anwendung mehrere Threads hat, die Anwendung insg. mehr Rechenzeit bekommt, weil alle Threads je nach Priorität einen bestimmten von der Rechenzeit bekommt. Und je mehr Threads man hat umso mehr Anteile werden ja in Anspruch genommen. Aber man kann aus einer cpu nicht mehr rausholen als drinn ist ;).

Interessant wäre jetzt:
-Für welche Aufgaben sollte man Threads nutzen
-Syncronisieren von Threads(also Critic, Mutex, Events usw.)
-Was ist zu beachten bei komplexen Strukturen(z.b. list, map, komplexe Datentypen)
-Programmdesign: wie sollte/könnte ein Programm mit Multithread aufgebaut sein(basierend auf dem ganzen Vorwissen)

Also Thumb up und frisch ans Werk ;)

P.S: @Das Gurke wozu sollte man libs brauchen? o_O Einzig für Linux/Unix/Mac muss man etwas in richtung posix einbinden.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

FlyingDragon

Frischling

  • »FlyingDragon« ist der Autor dieses Themas

Beiträge: 38

Wohnort: Sachsen

Beruf: Consultant / Software Architekt

  • Private Nachricht senden

6

05.11.2006, 13:53

Hallo,

mein Ziel war es einen kurzen Einblick in Threads zu geben ohne komplizierte Themen wie Priorität und CriticleSection anzusprechen. Ich wollte vorallem Anfängern das benutzen von Threads schmackhaft machen, in dem ich zeige wie einfach es doch sein kann. ;)

Was vieleicht noch im ersten Teil fehlt, sind ein paar Tipps zum Umgang. Das werd ich noch mit einbauen. Ich werde auf jeden Fall noch einen zweiten Teil schreiben der Dinge wie Priorität und CriticleSections bespricht.

Threads und C++
Mal sehen, vieleicht schreib ich dazu noch was. Ich hatte mir auch mal eine kleine Threadklasse programmiert, die könnte ich vorstellen.
Achtung! Das Lesen dieses Beitrags kann Ihnen und den Menschen in ihrer Umgebung erheblichen Schaden zufügen.
___________
Mein Leben, Freunde und die Spieleentwicklung

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

7

05.11.2006, 14:04

Naja ich empfinde es mit Threading ähnlich wie mit Netzwerkprogrammierung; Die Basics sind umglaublich einfach, aber das was es eigentliche ausmacht, ist dann doch unglaublich komplex :)
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

FlyingDragon

Frischling

  • »FlyingDragon« ist der Autor dieses Themas

Beiträge: 38

Wohnort: Sachsen

Beruf: Consultant / Software Architekt

  • Private Nachricht senden

8

05.11.2006, 16:24

Ok, ich hab das Tut erweitert. Leider gibt es Probleme mit den Formatierungstags. Es wäre schon, wenn ihr mein Tut in das Tutorial-Forum verschieben würdet.
Achtung! Das Lesen dieses Beitrags kann Ihnen und den Menschen in ihrer Umgebung erheblichen Schaden zufügen.
___________
Mein Leben, Freunde und die Spieleentwicklung

Osram

Alter Hase

Beiträge: 889

Wohnort: Weissenthurm

Beruf: SW Entwickler

  • Private Nachricht senden

9

05.11.2006, 17:59

Welche Formatierungsprobleme?
Übrigens wäre auch etwas zum allgemeinen Debuggen von MT Applikationen nett :).
"Games are algorithmic entertainment."

rewb0rn

Supermoderator

Beiträge: 2 773

Wohnort: Berlin

Beruf: Indie Game Dev

  • Private Nachricht senden

10

05.11.2006, 18:06

kommt sofort ;)
edit: ach is schon^^

Werbeanzeige