For loop und pthread



  • Hallo ihr Lieben,

    nach langer Zeit habe ich auch mal wieder eine Frage:

    Ich versuche mich gerade am Parallelisieren mittels pthread und möchte dabei direkt ein paar Argumente übergeben.

    Hier der Quellcode:

    #include <iostream>
    #include <pthread.h>
    #include <string>
    #include <vector>
    
    struct DATA {
    
      DATA(
    	  const std::string& s,
    		const unsigned int& i):
          dummy( s ), id(i) {};
    
    	const std::string dummy;
    	const unsigned int id;
    };
    
    void* foo(void* arg) {
    
    	DATA* data = static_cast<DATA*>(arg);
    
    	std::cout << "Thread Nr. " << data -> id 
    		        << " gives " << data -> dummy << std::endl;
    
      pthread_exit(NULL);
    }
    
    int main() {
    
      std::vector<std::string> dummies = { "some","stuff","in","here" };
    
    	pthread_t threads[ dummies.size() ];
    
    	for( unsigned int i = 0; i < dummies.size(); ++i ) {
    
        DATA data( dummies[ i ], i );
    
        pthread_create( &threads[i], NULL, foo, (void *)&data );
      }
    
      pthread_exit(NULL);
    
    	return 0;
    }
    

    Der Knackpunkt ist, dass die Ausgabe dabei Müll ist, z.B.

    Thread Nr. Thread Nr. 33 gives  gives herehere
    3read Nr. Thread Nr. 3 gives hereH,D\�`�<� �<��`��� ����`{<� {<��`k�� k��!�f�f�<�s��f�f�<�s�
    

    Dabei habe ich mich an diesem Beispiel orientiert.

    Jetzt kommt aber das schöne, ich habe noch dieses Tutorial gefunden und da wird explizit darauf hingewiesen, dass in C++, der Syntax von foo anders aussehen soll, also passe ich entsprechend an:

    void foo(void* arg) {
    ...
    
    pthread_create( &threads[i], NULL, (void*)&foo, (void *)&data );
    ...
    

    Dafür kriege ich jetzt die Fehlermeldung

    warning: ISO C++ forbids casting between pointer-to-function and pointer-to-object [enabled by default]
    error: invalid conversion from ‘void*’ to ‘void* (*)(void*)’ [-fpermissive]
    

    So, und wenn ich danach suche, dann kriege ich irgendwie alles mögliche an 'richtig' und 'falsch'.

    Kann mir bitte jemand erklären was genau das Problem ist?

    Viele Grüße,
    Klaus.



  • Hi!

    Ohne mich zu sehr mit dem Code befasst zu haben, zwei Anmerkungen zu deinem Code:

    1. Die Textausgabe via cout im Thread-Code ist nicht synchronisiert. Es ist daher je nach Implementation völlig normal, dass die Ausgabe verschiedener Threads ineinander verschachtelt ist. Um das zu vermeiden, solltest du vielleicht das cout mit einem Mutex schützen, so dass immer nur ein Thread eine "Zeile" text ausgeben kann.

    2. Beim Erstellen des Threads in Zeile 38 übergibst du einen Pointer auf data , bei dem es sich jedoch um eine lokale Variable handelt, die höchstens bis zum abschließenden " } " der Schleife lebt. Die Threads geben daher Daten nicht mehr existierender Objekte aus. Ich vermute, dass bei jedem Schleifendurchlauf derselbe (Stack-) Speicherbereich verwendet wird, um die DATA -Instanz zu erzeugen, daher geben auch alle Threads die Nummer 3 aus - wobei es Zufall ist, dass das überhaupt halbwegs funktioniert, da wie gesagt, die übergebenen DATA-Objekte nichtmehr existieren, wenn foo() im anderen Thread zur Ausführung kommt.

    Gruss,
    Finnegan



  • Hallo Finnega,

    Finnegan schrieb:

    Hi!
    1. Die Textausgabe via cout im Thread-Code ist nicht synchronisiert.

    Ja, no problem with that so far! 🙂

    Finnegan schrieb:

    2. Beim Erstellen des Threads in Zeile 38 übergibst du einen Pointer auf data , bei dem es sich jedoch um eine lokale Variable handelt, die höchstens bis zum abschließenden " } " der Schleife lebt. Die Threads geben daher Daten nicht mehr existierender Objekte aus. Ich vermute, dass bei jedem Schleifendurchlauf derselbe (Stack-) Speicherbereich verwendet wird, um die DATA -Instanz zu erzeugen, daher geben auch alle Threads die Nummer 3 aus - wobei es Zufall ist, dass das überhaupt halbwegs funktioniert, da wie gesagt, die übergebenen DATA-Objekte nichtmehr existieren, wenn foo() im anderen Thread zur Ausführung kommt.

    Daran bastle ich auch gerade herum. Ich habe jetzt vorher einen Vektor erzeugt, der alle relevanten DATA erhält und in dem for-loop dann mittels des Laufindex darauf zugegriffen wird.

    Ich probier mich also weiter ... 🙂

    Gruß,
    Klaus.



  • Klaus82 schrieb:

    Daran bastle ich auch gerade herum. Ich habe jetzt vorher einen Vektor erzeugt, der alle relevanten DATA erhält und in dem for-loop dann mittels des Laufindex darauf zugegriffen wird.

    Ich habe selbst auch schon öfter mit solchen C-Interfaces zu tun gehabt, denen man User-Daten nur über einen void* übergeben konnte, und bei denen die benötigte Lebensdauer der Objekte nicht genau vorherbestimmbar war (asynchrone Verarbeitung in anderen Threads), und auch noch mehrere Threads auf die selben Objekte zugreifen mussten. In diesem Fall hat sich oft ein std::shared_ptr bewährt, den ich - was einem auf den ersten Blick die Haare zu Berge stehen lassen sollte - via new erzeugt habe:

    auto object = std::make_shared<Object>();
       ...
       std::shared_ptr<Object>* userData = new std::shared_ptr<Object>(object);
       asynchrones_c_interface(userData);
    

    ... das allerdings nur um den shared_ptr durch das C-Interface zu "tunneln". In der Thread-Funktion (bei der garantiert sein sollte, dass diese auch ausgeführt wird, sonst leckt es) wurde dieser dann direkt gelöscht, um möglichst wenig mit nackten, "owning" Pointern herumhantieren zu müssen:

    void thread_function(void* userData)
       {
          auto objectPtr = static_cast<std::shared_ptr<Object>*>(userData);
          std::shared_ptr<Object> object = *objectPtr;
          delete objectPtr;
          ...
          // mach was mit object.
       }
    

    Vielleicht ist ja ein ähnliches Pattern auch für dein Program nützlich.
    Ansonsten empfiehlt sich vielleicht auch eher, std::thread zu benutzen, falls du darauf Zugriff hast, dort kann man dann die shared_ptr direkt an die Thread-Funktion übergeben ohne dieses etwas seltsame shared_ptr -Pointer-Gedöns 😃

    Gruss,
    Finnegan



  • Finnegan schrieb:

    void thread_function(void* userData)
       {
          auto objectPtr = static_cast<std::shared_ptr<Object>*>(userData);
          std::shared_ptr<Object> object = *objectPtr;
          delete objectPtr;
          ...
          // mach was mit object.
       }
    

    Vielleicht gibt es Fälle, wo man einen shared_ptr braucht, aber der default sollte unique_ptr sein.

    auto object = std::make_unique<Object>();
    asynchrones_c_interface(object.release());
    
    void thread_function(void* userData) {
        auto object = unique_ptr<Object>(static_cast<Object*>(userData));
        // mach was mit object.
    }
    


  • Benutze std::thread oder boost::thread!


Anmelden zum Antworten