Frage zum Multithreading



  • HI.

    ich habe hier folgenden code:

    const int MAX_THREADS = 2;
    const int BILD_ANZ = 400;
    
    DWORD WINAPI ThreadFunc1(LPVOID data)
    {
    	for (int i=0;i<BILD_ANZ;i++)
    	{
    		bild.Analyse(i);
    		i++;
    	}
    	return((DWORD)data);
    }
    
    DWORD WINAPI ThreadFunc2(LPVOID data)
    {
    	for (int i=1;i<BILD_ANZ;i++)
    	{
    		bild.Analyse(i);
    		i++;
    	}
    	return((DWORD)data);
    }
    
    int main()
    {
    	int i;
    	cBild bild;
    
    	bild.LoadData(BILD_ANZ);
    
    	HANDLE hThread[MAX_THREADS];
    	DWORD  dwThreadID[MAX_THREADS];
    
    	hThread[0] = CreateThread(NULL,0,ThreadFunc1,(LPVOID)0,0,dwThreadID[0]);	
    	hThread[1] = CreateThread(NULL,0,ThreadFunc2,(LPVOID)1,0,dwThreadID[1]);	
    
    	WaitForMultipleObjects(	MAX_THREADS,hThread,TRUE,INFINITE);
    
    	for (i=0; i<MAX_THREADS; i++)
    	   CloseHandle(hThread[i]); 
    
    	_getch();
        return 0;
    }
    

    meine frage dazu: wie bekomme ich jetzt 'bild' in die beiden threads? das problem ist nämlich, dass die bildanalyse pro bild etwa 2 sekunden dauert, was bei 400 bilder gute 13 minuten dauert. das wollte ich gerne mit 2 threads, die gleichzeitig bearbeitet werden, verkürzen.

    VIELEN DANK.
    STICK.



  • okay. ich habe das ganze etwas überarbeitet und aus den threads eine eigene klasse gemacht. das sieht jetzt so aus:

    cThread.h:

    #ifndef CTHREAD_H
    #define CTHREAD_H
    #pragma once
    
    #include <windows.h>
    #include <iostream>
    #include <vector>
    
    DWORD WINAPI ThreadFunc1(LPVOID data);
    DWORD WINAPI ThreadFunc2(LPVOID data);
    
    class cThread
    {
    public:
    	cThread(void);
    	void StartThread (int type);
    	void WaitThread  ()
    	{
    		WaitForMultipleObjects(	2,itsThread,TRUE,INFINITE);			        }
    	void CloseThread ()
    	{
    		CloseHandle(itsThread[0]);
    		CloseHandle(itsThread[1]);
    	}
    	~cThread(void);
    private:
    	HANDLE itsThread[2];
    	std::vector <int> itsVal;
    	DWORD  itsThreadID[2];
    };
    #endif
    

    cThread.cpp:

    #include "cThread.h"
    
    using namespace std;
    
    cThread::cThread(void)
    {
    }
    
    cThread::~cThread(void)
    {
    }
    
    void cThread::StartThread (int type)
    {
    	if (!type)
    		itsThread[type] = CreateThread(NULL,0,ThreadFunc1,(LPVOID)type,0,&itsThreadID[type]);
    	if (type == 1)
    		itsThread[type] = CreateThread(NULL,0,ThreadFunc2,(LPVOID)type,0,&itsThreadID[type]);
    }
    DWORD WINAPI ThreadFunc1(LPVOID data)
    {
    	for (int i=0;i<20;i++)
    		cout << "Func1\n";
    	return((DWORD)data);
    }
    DWORD WINAPI ThreadFunc2(LPVOID data)
    {
    	for (int i=0;i<20;i++)
    		cout << "Func2\n";
    	return((DWORD)data);
    }
    

    und Main.cpp

    int main()
    {
    	int i;
    	cThread data;
    
    	data.StartThread(0);
    	data.StartThread(1);
    
    	for (int i=0;i<5;i++)
    		cout << "main\n";
    
    	data.WaitThread();
    	data.CloseThread();
    
    	_getch();
        return 0;
    }
    

    1.) anstelle von 'bild' habe ich jetzt einen vector gesetzt.
    2.) nun habe ich aber das problem, wie ich aus den thread-funktionen member der klasse machen kann. das bekomme ich nicht hin.

    DANKE!
    STICK.



  • Erstens: Multithreading bringt nur auf Mehr-Prozessor-Systemen einen Geschwindigkeitsvorteil. Auf "normalen" Rechnern müssen die Threads sowieso nacheinander verarbeitet werden - so daß du eher langsamer wirst.

    Zweitens: Wozu brauchst du mehrere Funktionen, die alle das selbe machen?

    Drittens: Der LPVOID-Parameter ist der typische Weg, Daten an den Thread zu übergeben:

    struct th_data
    {
      cBild& bild;
      size_t begin,step,end;
    
      th_data(cBild& b,size_t bg,size_t st,size_t en)
      : bild(b),begin(bg),step(st),end(en) {}
    };
    
    DWORD WINAPI ThFunc(LPVOID data)
    {
      th_data* pdata = (th_data*)data;
      for(int i=pdata->begin;i<pdata->end;i+=pdata->step)
        pdata->bild.Analyse(i);
      delete pdata;
      return 0;
    }
    
    ...
    for(i=0;i<MAX_THREADS;++i)
      hThreads[i] = CreateThread(NULL,0,ThFunc,new th_data(bild,i,MAX_THREADS,BILD_ANZ),0,dwThreads+i);
    ...
    


  • DANKE für die antwort! ich habe dadurch einiges besser verstanden! das auf normalen rechnern die threads nacheinander bearbeitet werde war mir klar. ich wollte trotzdem mal wenigsten ein bisschen in dem bereich multithreading reinschnuppern. kann ja nie verkehrt sein 🙂

    ich habe mein programm jetzt so verändert, wie du es vorgeschlagen hast. ich habe jetzt aber dazu eine frage. warum sind am ende in meinem vector erst alle nullen und dann die einsen. ich dachte, dass der rechner zwischen thread 1 und 2 ständig hin- und herspringt und sich dadurch nullen und einsen abwechseln müssten. oder werden sie doch richtig sturr wie normale funktionen nacheinander abgearbeitet (also bei 'normalen rechnern' mit nur einem kern)?

    DANKE.
    STICK.

    hier meine cBILDER-klasse:

    class cBILDER
    {
    public:
    	cBILDER(void);
    	~cBILDER(void);
    	void SVal (double val)
    	{
    		itsVal.push_back(val);
    	}
    	double GVal (int type)
    	{
    		return itsVal[type];
    	}
    	int Size()
    	{
    		return itsVal.size();
    	}
    private:
    	std::vector <int> itsVal;
    };
    

    hier die main.cpp:

    const int MAX_THREADS = 2;
    const int BILD_ANZ = 2;
    
    struct th_data
    {
      cBILDER &bild;
      size_t maxim,wahl;
    
      th_data(cBILDER& b,size_t bg,size_t wh)
      : bild(b),maxim(bg),wahl(wh) {}
    }; 
    
    DWORD WINAPI ThFunc(LPVOID data)
    {
    	th_data* pdata = (th_data*)data;
    	for (int i=0;i<pdata->maxim;i++)
    		pdata->bild.SVal(pdata->wahl);
    	delete pdata;
    	return 0;
    }
    
    int main()
    {
    	int i;
    	cBILDER bild;
    
    	HANDLE hThread[MAX_THREADS]; 
    	DWORD  dwThreadID[MAX_THREADS];
    
    	for(i=0;i<MAX_THREADS;++i)
    		hThread[i] = CreateThread(NULL,0,ThFunc,new th_data(bild,50000,i),0,dwThreadID+i);
    
    	WaitForMultipleObjects (MAX_THREADS,hThread,TRUE,INFINITE);
    
        for (i=0; i<MAX_THREADS; i++)
           CloseHandle(hThread[i]);
    
    	for (i=0;i<bild.Size();i++)
    		cout << " " << bild.GVal(i);
    
    	_getch();
        return 0;
    }
    


  • In welcher Reihenfolge die verschiedenen Threads abgearbeitet werden, entscheidet der Prozessor zur Laufzeit. Da kann es gut sein, daß erst ein Thread komplett abgearbeitet wird, bevor der nächste drankommt.

    (Ehe ich es vergesse: Die Zugriffe auf gemeinsame Daten solltest du synchronisieren - es kann leicht ins Auge gehen, wenn Thread1 mitten im push_back() schlafengelegt wird und dann Thread2 versucht, in deinen vector zu schreiben)



  • CStoll schrieb:

    Die Zugriffe auf gemeinsame Daten solltest du synchronisieren - es kann leicht ins Auge gehen, wenn Thread1 mitten im push_back() schlafengelegt wird und dann Thread2 versucht, in deinen vector zu schreiben

    ja. das war das, was ich als nächstes angehen wollte 😉
    grüsse.
    STICK.



  • Hi,

    ist ja lustig - da mache ich mir auch gerade Gedanken drüber....

    CStoll schrieb:

    ...

    ...
    DWORD WINAPI ThFunc(LPVOID data)
    {
      th_data* pdata = (th_data*)data;
    ...
      delete pdata;
      return 0;
    }
    
    ...
    CreateThread(NULL,0,ThFunc,new th_data(bild,i,MAX_THREADS,BILD_ANZ),0,dwThreads+i);
    ...
    

    Was ich ja dabei nicht so schön finde, ist dass Erzeuger und Vernichter getrennt sind (konkret kracht natürlich ThFunc(), wenn man ihm ein StackObjekt übergibt) - aber so eine richtig nette Alternative fällt mir auch nicht ein:

    class observed_th_Data {
       th_Data const& data; // Könnte auch Kopie sein ... kostet aber mehr
       bool isCopiedFlag;
    public:
       observed_th_Data(th_Data const& t) : data(t), isCopiedFlag(false) {}
       bool isCopied() const { MyThreadLock m; return isCopiedFlag; }
       th_Data get_th_data() {
          MyThreadLock m; // muss man noch implementieren
          isCopiedFlag = true; // sollte man erst nach erfolgreicher Kopie-Rückgabe machen - habe das hier verkürzt
          return data; 
       }
    };
    
    DWORD WINAPI ThFunc(LPVOID data)
    {
       observed_th_Data* ob_pdata = (observed_th_Data*)data;
       th_Data tData(ob_pdata->get_th_data());
    ...
      return 0;
    }
    
    int main() {
       th_Data tData(bild,i,MAX_THREADS,BILD_ANZ);
       observed_th_Data obTData(tData);
    ...
       CreateThread(NULL,0,ThFunc,&obTData,0,dwThreads+i);
       while(!obTData.isCopied()) { 
          MySleep(100); // SleepFunktion noch nachschlagen + "TimeOut" einbauen.
       }
       return 0;
    }
    

    Nachteile:
    - Threadsynchronisation nötig
    - Kopie (großer) Objekte erzeugt Overhead
    - Funktioniert nur für "Kopierbare Objekte"
    - Gefahr, dass der Hauptthread "ewig" hängt (alternativ: Dass er dem anderen Thread die Ressource entzieht)
    - ...

    Die Probleme hängen halt einfach prinzipiell damit zusammen, dass mehrere Threads dieselben Daten/Objekte verwenden...
    Weiß jemand einen besseren Weg ? Oder ist der "Ownership"-Wechsel letztlich doch das geringere Übel ?

    Gruß,

    Simon2.



  • Dieser Thread wurde von Moderator/in HumeSikkins aus dem Forum C++ in das Forum WinAPI verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • Ähm, was ist das denn bitte für ein Schwachsinn ?

    Man benutzt Threads unter anderem, um komplexe Daten zu berechnen, was hat das mit ner Kopie zu tun ? Die Daten werden doch meistens im Hauptthread vorbereitet und dann zur Verarbeitung an den Thread weitergereicht.

    Simon2 schrieb:

    Die Probleme hängen halt einfach prinzipiell damit zusammen, dass mehrere Threads dieselben Daten/Objekte verwenden...
    Weiß jemand einen besseren Weg ? Oder ist der "Ownership"-Wechsel letztlich doch das geringere Übel ?

    In den meisten Fällen ist eine Kopie einfach nicht sinnvoll. Da musst du synchen.



  • CodeFinder schrieb:

    Ähm, was ist das denn bitte für ein Schwachsinn ?

    Man benutzt Threads unter anderem, um komplexe Daten zu berechnen, was hat das mit ner Kopie zu tun ? Die Daten werden doch meistens im Hauptthread vorbereitet und dann zur Verarbeitung an den Thread weitergereicht.

    Simon2 schrieb:

    Die Probleme hängen halt einfach prinzipiell damit zusammen, dass mehrere Threads dieselben Daten/Objekte verwenden...
    Weiß jemand einen besseren Weg ? Oder ist der "Ownership"-Wechsel letztlich doch das geringere Übel ?

    In den meisten Fällen ist eine Kopie einfach nicht sinnvoll. Da musst du synchen.

    Sorry, ist gar kein Schwachsinn !
    (Tut es wirklich not, verbal hier so auf die K** zu hauen ? Hättest Du das auch getan, wenn wir uns bei einer Tasse Kaffe "Aug' in Auge" darüber unterhalten hätten ?)
    🙄

    Was bedeutet denn "weiterreichen" im Programmcode ?

    Es ist eine Frage der Programmarchitektur: Ob jetzt eine Funktionalität in einem separaten Thread läuft oder nicht ... selbst bei einem "normalen" (synchronen) Funktionscall muss man sich überlegen, ob man per Kopie, (const) Referenz oder Zeiger übergeben will.
    Sprich: Wer soll an dem Teil rumändern ? Oder anders gesagt: "Ownership".

    Hakeliger wird Ownership hier durch 2 Umstände:

    • Erst durch das Multithreading kommt erst die Frage nach dem gleichzeitigen Zugriff: Jetzt kann sich selbst der Aufgerufene nicht mehr sicher sein, dass er (in seiner Bearbeitungsspann) der einzige Veränderer ist.
    • Durch die vorgegebene Schnittstelle (via void*) hat man keine Möglichkeiten, ownership-Konventionen mit Sprachmitteln festzulegen (z.B. durch Verwendung von const etc.).

    Mal ganz praktisch gefragt: Warum teilen sich Haupt-und Childthread überhaupt Daten ?

    Vielleicht wird Dir das Problem auch klarer, wenn Du Dir vorstellst, dass Du die "Hauptthreadroutine" erstellst und ich die "Childthreadroutine" schreibe.... worüber müssten wir uns dann unterhalten ?

    Gruß,

    Simon2.


Log in to reply