Forum: PC-Programmierung C, jeder Core ein Thread, Teilergebnis sekündlich ausgeben


von Timmo H. (masterfx)


Lesenswert?

Hallo. Ich habe ein kleines Problem mit Threads unter Windows. Ich lese 
zunächst die Anzahl der Cores aus (GetSystemInfo) und erstelle dann pro 
Core einen Thread. Diese Threads sollen dann zeitgleich gestartet werden 
und immer eine Sekunde lang Berechnungen durchführen. Jede Sekunden wird 
dann durch ein Timer-Event ein Label aktualisiert, wo die Summe der 
Schleifendurchläufe Aller Threads drin stehen soll.
Das Problem ist, dass ich nicht weiß wie ich dem Timer-Event mitteilen 
kann, dass ALLE Threads ihre erste Berechnung abgeschlossen haben, und 
den Threads selber dann Mitteilen kann, dass sie nun weiterrechnen 
können. Ich habe exemplarisch einfach mal die "bench_valid"-Variable 
dahin gemacht damit man sieht wie ich das meine.
Ich könnte jetzt natürlich ein Array machen wo jeder Thread dann seine 
Variable auf "1" setzt, das ließe sich aber schlecht im Timer-Event 
abfragen. Oder ich nehme eine short-Variable wobei jeder Thread ein Bit 
setzen kann. Dann müsste ich jedoch mit Mutexen arbeiten, und würde dann 
im Timer-Event auf "0xF" prüfen. Aber vielleicht gehts ja auch viel 
einfacher. Bin für jeden Tipp dankbar.

Momentan siehts in etwa so aus (stark gekürzt):
1
HANDLE ghStartThreads; 
2
int perf_counter[16] = {0}; /* Max 16 Cores */
3
4
int WINAPI WinMain(...){
5
6
  hDialog = CreateDialog (hThisInstance, MAKEINTRESOURCE (DLG_MAIN), 0, (DLGPROC)WindowProcedure);
7
  GetSystemInfo( &sysinfo );
8
  numCPU = sysinfo.dwNumberOfProcessors;
9
  sprintf(buf,"%d",numCPU);
10
  SetWindowText(GetDlgItem(hDialog,IDC_CPUS),buf);
11
12
  /* Textfelder alle 100ms aktualisieren */
13
  myTimer = SetTimer ( hDialog, NULL, 100, NULL );
14
  ghStartThreads =  CreateEvent(NULL, TRUE, FALSE, TEXT("StartThreads")); 
15
16
  for(perf_index = 0; perf_index < numCPU; perf_index++){
17
    hThread[perf_index] = CreateThread(NULL, 0, (void *)calc, (void *)perf_index,0,NULL);
18
    SetThreadPriority(hThread[perf_index],THREAD_PRIORITY_BELOW_NORMAL);
19
  }
20
  SetEvent(ghStartThreads);
21
  while (GetMessage (&messages, NULL, 0, 0)){
22
    TranslateMessage(&messages);
23
    DispatchMessage(&messages);
24
  }
25
26
27
  return messages.wParam;
28
}
29
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
30
{
31
  char count_str[10];
32
  int bench_sum,i;
33
    switch (message)                  /* handle the messages */
34
    {
35
        case WM_DESTROY:
36
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
37
            break;
38
    case WM_CLOSE:
39
40
          DestroyWindow (hwnd);
41
          return 1;
42
        break;
43
        
44
      case WM_TIMER:
45
      if(bench_valid == 1){ /* <== Das muss für alle Threads gelten!*/
46
        bench_sum = 0;
47
        for(i = 0; i < numCPUS; i++){
48
          bench_sum += perf_counter[i];
49
        }
50
        sprintf(count_str,"%5d",bench_sum);
51
        SetWindowText(GetDlgItem(MainDlg,IDC_PERF),count_str);
52
        bench_valid=0;
53
54
      }
55
      break;
56
        default:                      /* for messages that we don't deal with */
57
            return 0;
58
    }
59
60
    return 0;
61
}
62
63
int calc(int index){
64
  int time1, i;
65
  volatile int a,b;
66
67
  /* Wait for start signal */
68
  WaitForSingleObject(ghStartThreads, INFINITE);
69
    
70
  while(1){
71
    while(bench_valid); /* <== das muss für jeden Thread separat sein*/
72
    perf_counter[index] = 0;
73
    time1 = GetTickCount();
74
    do{
75
      //HIER SIND JETZT DIE BERECHNUNGEN
76
      perf_counter[index] += 1;
77
      
78
    }while( ( GetTickCount() - time1) < 1000);
79
    bench_valid=1;
80
81
  }
82
}

von Klaus W. (mfgkw)


Lesenswert?

Nachdem eh jeder Thread seinen Index hat, kannst du doch noch ein
Feld mit entsprechend vielen Statusbytes definieren.
Zu Anfang werden alle auf 0 gesetzt, jeder Thread setzt seinen
Eintrag wenn er fertig ist z.B. auf 1.
Daran sieht das Hauptprogramm, daß der jeweilige Thread durch ist.
Wenn es ein Eintrag ist, der atomar gesetzt/gelesen werden kann
(das wäre es bei char oder auf PCs auch bei short und int),
braucht man dafür keinen Mutex.
Wenn sie weiter rechnen sollen, halt wieder auf 0 setzen oder Event
schicken.

Alternativ: jeder Thread löst am Ende ein Event aus.

Und volatile nicht vergessen für globale Variablen...

PS: Tippfehler korrigiert...

von Timmo H. (masterfx)


Lesenswert?

Wenn ich jetzt ein Feld mache dann müsste ich ja im Timer-Event im 
schlechtesten fall 16 Variablen überprüfen, das erscheint mir etwas zu 
umständlich. Oder wie meintest du das?

von Klaus W. (mfgkw)


Lesenswert?

ja, so meinte ich das.
(Eine andere Möglichkeit wäre, nur eine Variable zu nehmen, die
von jedem Thread um eins hochgezählt wird. Das erfordert dann
aber wieder einen Mutex und ist unterm Strich ineffizienter.)

Das Hauptprogramm braucht aber doch gar nicht bei jedem Test
durch alle Feldelemente laufen; es testet den ersten und bleibt
stehen, wenn der noch nicht da ist.
Erst wenn der erste Thread sein Ende signalisiert, wird der
zweite getestet und ggf. wieder stehen geblieben usw.; bis
man beim letzten ankommt. Dann sind alle Threads zu Ende.
Bereits positiv getestete kann man so auslassen.

von Timmo H. (masterfx)


Lesenswert?

Ja das stimmt wohl. Na dann werde ich das erstmal so machen.
Danke erstmal soweit

von Timmo H. (masterfx)


Lesenswert?

Habs vorhin mal mit Mutexen und mit einem Array versucht. Du hattest 
recht, Array ist in diesem Fall sehr viel schneller. CPU Auslastung mit 
Mutex ca. 93%, mit Array 99%

von Oliver R. (superberti)


Lesenswert?

Hallo,

was wollt ihr immer bloß mit Mutexen? Dafür nimmt man eine Critical 
Section oder deren Abkömmlinge, die sind deutlich leichtgewichtiger.
Und in Multi-Core-Umgebungen garantiert Dir niemand mehr, dass 
Integer-Zugriffe atomar sind. Außerdem garantiert kein Compiler, dass 
"++" wirklich atomar ist. Schließlich kann durch Optimierung die 
Varaiable in einem Register gehalten werden, dort erhöht werden und erst 
später zurückgeschrieben werden. Ein anderer Thread auf einem zweiten 
Core würde dann ein inkonsistentes Ergebnis lesen!
Dafür gibt es dann die Api-Funktionen InterlockedIncrement/Decrement 
usw., die sollte man in jedem Fall dafür anwenden (sind dann ja auch 
Lock-Free).
Bei der Thread-Programmierung bietet sich C++ (von mir aus auch C#) 
geradezu an, da man in einer (Thread)-Klasse alle threadspezifischen 
Variablen sehr gut kapseln kann und alle threadübergreifenden Variablen 
als statische Klassenvariablen deklarieren kann, die dann durch eine CS 
geschützt werden.
Der Code wird dadurch jedenfalls wesentlich übersichtlicher und besser 
verwaltbar.
In reinem C hat man dann solche Konstrukte wie TLS oder Arrays, 
spätestens wenn man dynamisch neue Threads hinzufügt oder entfernt 
wird's echt haarig.
Nur so als Anregung...

Gruß,

von Klaus W. (mfgkw)


Lesenswert?

Oliver R. schrieb:
> Hallo,
>
> was wollt ihr immer bloß mit Mutexen? Dafür nimmt man eine Critical
> Section oder deren Abkömmlinge, die sind deutlich leichtgewichtiger.

Lt. http://de.wikipedia.org/wiki/Mutex
    * Ein kritischer Abschnitt (engl. critical section oder critical 
region) ist derjenige Teil im ausführbaren Code, in dem ein wegen des 
Mutex ungestörter Zugriff auf Daten erfolgt. Ein kritischer Abschnitt 
darf nicht unterbrochen werden

        * von einem anderen Thread, der auf dieselben über Mutex 
geschützten Daten zugreifen will,
        * von einem anderen Thread, der den Mutex-Zugriff möglicherweise 
unzulässig verlängern würde, da er den zugreifenden Thread längere Zeit 
unterbricht.



> Und in Multi-Core-Umgebungen garantiert Dir niemand mehr, dass
> Integer-Zugriffe atomar sind.

Zuweisung an char aber sicher.

> Außerdem garantiert kein Compiler, dass
> "++" wirklich atomar ist.

Hat auch niemand behauptet.
Ich sagte, wenn man es mit einer Variablen löst, die von
allen Threads hochgezählt wird, dann braucht man einen Mutex.
Eben weil das nicht atomar ist.

> ...
> Bei der Thread-Programmierung bietet sich C++ (von mir aus auch C#)
> geradezu an, da man in einer (Thread)-Klasse alle threadspezifischen

Stimmt.

> Variablen sehr gut kapseln kann und alle threadübergreifenden Variablen
> als statische Klassenvariablen deklarieren kann, die dann durch eine CS
> geschützt werden.

Oder durch einen Mutex, je nachdem, wie man das Biest nennt,,,

> Der Code wird dadurch jedenfalls wesentlich übersichtlicher und besser
> verwaltbar.
> In reinem C hat man dann solche Konstrukte wie TLS oder Arrays,
> spätestens wenn man dynamisch neue Threads hinzufügt oder entfernt
> wird's echt haarig.
> Nur so als Anregung...
>
> Gruß,

von (prx) A. K. (prx)


Lesenswert?

Oliver R. schrieb:

> was wollt ihr immer bloß mit Mutexen? Dafür nimmt man eine Critical
> Section oder deren Abkömmlinge, die sind deutlich leichtgewichtiger.

Oder das, was für sehr kurze Locks um Variable herum tatsächlich 
sinnvoll ist, weil es dabei fast keine Resourcen frisst: Spinlocks.

von (prx) A. K. (prx)


Lesenswert?

Für Windows hat MS ein ganzes Kapitel für sowas verbrochen:
http://msdn.microsoft.com/en-us/library/ms686353(VS.85).aspx

Für atomaren Zugriff findet sich dort eine eigene Funktionsgruppe:
http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx

von Oliver R. (superberti)


Lesenswert?

Hallo,

>>
>> was wollt ihr immer bloß mit Mutexen? Dafür nimmt man eine Critical
>> Section oder deren Abkömmlinge, die sind deutlich leichtgewichtiger.
>
> Lt. http://de.wikipedia.org/wiki/Mutex
>     * Ein kritischer Abschnitt (engl. critical section oder critical
> region) ist derjenige Teil im ausführbaren Code, in dem ein wegen des
> Mutex ungestörter Zugriff auf Daten erfolgt. Ein kritischer Abschnitt
> darf nicht unterbrochen werden
>
>         * von einem anderen Thread, der auf dieselben über Mutex
> geschützten Daten zugreifen will,
>         * von einem anderen Thread, der den Mutex-Zugriff möglicherweise
> unzulässig verlängern würde, da er den zugreifenden Thread längere Zeit
> unterbricht.
>

Schön abgeschrieben, aber da wir hier WINDOWS-Programmierung machen 
besteht sehr wohl ein großer Unterschied zwischen einer CriticalSection 
und einem Mutex. Der Bedeutung nach ist eine CriticalSection nach 
natürlich auch ein Mutex, bei der Windows-Programmierung unterscheidet 
man aber sehr wohl zwischen beiden (CreateMutex(...) / 
InitializeCriticalSectionAndSpinCount(...))

>
>
>> Und in Multi-Core-Umgebungen garantiert Dir niemand mehr, dass
>> Integer-Zugriffe atomar sind.
>
> Zuweisung an char aber sicher.

Nein, sind sie nicht. Es gibt im X86-ASM durchaus atomare Funktionen, 
aber niemand garantiert, dass der Compiler sie auch verwendet. Der 
"++"-Operator war nur ein Beispiel.

Gruß,

von (prx) A. K. (prx)


Lesenswert?

Oliver R. schrieb:

> Nein, sind sie nicht. Es gibt im X86-ASM durchaus atomare Funktionen,
> aber niemand garantiert, dass der Compiler sie auch verwendet.

Und genau deshalb existieren die erwähnten Interlocked... Funktionen.

von Oliver R. (superberti)


Lesenswert?

A. K. schrieb:
> Oliver R. schrieb:
>
>> was wollt ihr immer bloß mit Mutexen? Dafür nimmt man eine Critical
>> Section oder deren Abkömmlinge, die sind deutlich leichtgewichtiger.
>
> Oder das, was für sehr kurze Locks um Variable herum tatsächlich
> sinnvoll ist, weil es dabei fast keine Resourcen frisst: Spinlocks.

Ja, wenn Du Treiber schreibst, dann kannst Du das machen. Macht hier 
aber gerade keiner! -> Kernel Mode

Gruß,

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Es gibt auch noch "Borders" in C falls er nur will das sich alle Threads 
"treffen" bevor es weitergeht...

von (prx) A. K. (prx)


Lesenswert?

Oliver R. schrieb:

> Ja, wenn Du Treiber schreibst, dann kannst Du das machen. Macht hier
> aber gerade keiner! -> Kernel Mode

Spinlocks sind eine allgemeine Synchronisationstechnik. Dafür braucht 
man keinen Kernel-Modus. Der Unterschied zu System-Mutexen besteht 
darin, dass konkurrierende Threads in der kritischen Phase auf 
Applikationsebene aktiv pollen, nicht sich wie in Mutexen schlafen 
legen. Wenn es, wie bei Variablenzugriffen, nur um ein paar Takte geht 
ist das viel billiger als jeder Systemcall.

Gibt natürlich ein paar Dinge die man beachten sollte. Wie Cache-Lines 
und kleine Delays (ggf. hinting NOPs genannt) um bei SMT/Hyperthreading 
dem SMT-Kollegen nicht auf den Füssen zu stehen.

von Oliver R. (superberti)


Lesenswert?

A. K. schrieb:
> Oliver R. schrieb:
>
>> Ja, wenn Du Treiber schreibst, dann kannst Du das machen. Macht hier
>> aber gerade keiner! -> Kernel Mode
>
> Spinlocks sind eine allgemeine Synchronisationstechnik. Dafür braucht
> man keinen Kernel-Modus.

Ich hätte auch schreiben können, das WINDOWS nur im Kernel-Modus dafür 
Funktionen bereitstellt. Es gibt wohl auch C/C++-Implementationen, die 
kommen aber ohne inline-ASM auch nicht aus. Mir wäre nicht wohl dabei.

Gruß,

von (prx) A. K. (prx)


Lesenswert?

Oliver R. schrieb:

> Ich hätte auch schreiben können, das WINDOWS nur im Kernel-Modus dafür
> Funktionen bereitstellt.

Im .Net Framework scheint es welche zu geben:
http://msdn.microsoft.com/en-us/library/dd460716(VS.100).aspx

Und sie lassen sich auch ohne Assembler implementieren:
http://msdn.microsoft.com/en-us/library/ms683590(VS.85).aspx

von (prx) A. K. (prx)


Lesenswert?

PS: Preview für .Net 4, dauert also wohl noch etwas...

von ... (Gast)


Lesenswert?

> Ich hätte auch schreiben können, das WINDOWS nur im Kernel-Modus dafür
> Funktionen bereitstellt.

Dann lies die Doku zu "InitializeCriticalSectionAndSpinCount".
Die Funktion heißt nicht umsonst so.

von Oliver R. (superberti)


Lesenswert?

... schrieb:
>> Ich hätte auch schreiben können, das WINDOWS nur im Kernel-Modus dafür
>> Funktionen bereitstellt.
>
> Dann lies die Doku zu "InitializeCriticalSectionAndSpinCount".
> Die Funktion heißt nicht umsonst so.

Manchmal frage ich mich, warum ich hier überhaupt noch schreibe.
Eine Critical Section im User Mode ist halt was ganz anderes als ein 
Spin-Lock im Kernel-Mode. Das ist auch ganz klar so im MSDN beschrieben. 
Aber drei Punkte stehen bekanntlich ja auch für Blindheit.

Gruß,

von Telefondesinfizierer (Gast)


Lesenswert?

> Oliver R. schrieb:
>
> > Nein, sind sie nicht. Es gibt im X86-ASM durchaus atomare Funktionen,
> > aber niemand garantiert, dass der Compiler sie auch verwendet.
>
> Und genau deshalb existieren die erwähnten Interlocked... Funktionen.

Wobei es jedoch kein InterlockedRead() bzw. InterlockedWrite() gibt, 
weil Microsoft (inkl. Compiler) den Lese-/Schreibzugriff auf (aligned) 
32 bit Variablen als atomar garantiert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.