iterieren über liste mit pthread_t schlägt fehl



  • Wahrscheinlich tragen sich die Threads selber aus der Liste aus, wenn sie sich beenden. Dann ist erstens der Iterator kaputt, wenn das Element, auf das er zeigt, nicht mehr in der Liste ist. Und zweitens hast du dann soundso Probleme, wenn beide Threads gleichzeitig auf die gleiche Liste zugreifen.



  • dies ist thread_helper.cpp:

    #include "thread_helper.h"
    #include <iostream>
    using namespace std;
    
    struct h { thread_helper *t; void *(*func)(void *); void *arg; };
    
    void *run_call(void *p)
    {
       static_cast<h*>(p)->t->run(static_cast<h*>(p)->func,static_cast<h*>(p)->arg);
    }
    
    thread_helper::thread_helper(int mt)
    {
       max_threads = mt;
    }
    
    thread_helper::~thread_helper()
    {
       join_all();
    }
    
    pthread_t thread_helper::create(void *(*func)(void *), void *arg)
    {
       pthread_t t;
       if(func)
       {
          int ret;
    
          if(threads.size() < max_threads || max_threads == -1)
          {
    	 h *tmp = new h;
    	 tmp->t = this;
    	 tmp->func = func;
    	 tmp->arg = arg;
    
    	 ret = pthread_create(&t, NULL, &run_call, tmp);
    	 if(!ret)
    	    threads.push_back(t);
          }
          else
    	 func(arg);
          return t;
       }
    }
    
    void thread_helper::run(void *(*func)(void *), void *arg)
    {
       func(arg);
    
       this->exit();
    }
    
    void thread_helper::exit()
    {
       pthread_t t = pthread_self();
       threads.remove(t);
       pthread_exit((void *) 0);
    }
    
    void thread_helper::join_all()
    {
       list<pthread_t>::const_iterator it;
    
       for(it = threads.begin(); it != threads.end(); it++)
    
          pthread_join(pthread_t(*it), NULL);
       threads.clear();
    }
    

    und das der zugehörige header thread_helper.h:

    #ifndef THREAD_HELPER_H
    #define THREAD_HELPER_H
    
    #include <list>
    using namespace std;
    #include <pthread.h>
    
    class thread_helper
    {
       friend void *run_call(void *);
    private:
       list<pthread_t> threads;
       int max_threads;
       void run(void *(*)(void *), void *);
    public:
       thread_helper(int mt = -1);
       ~thread_helper();
       pthread_t create(void *(*)(void *), void *);
       void join_all();
       void exit();
    };
    
    #endif
    

    falls jemand ein passendes testprogramm sucht(der code, indem die klasse wirklich verwendet wird ist zu lang um ihn hier abzudrucken...):

    #include <iostream>
    #include <unistd.h>
    #include "thread_helper.h"
    
    void *thread1(void * v)
    {
       for(int i = 0; i < 10; i++)
       {
          cout << "Schleif(1)!" << endl;
          sleep(1);
       }
    }
    
    void *thread2(void * v)
    {
       for(int i = 0; i < 10; i++)
       {
          cout << "Schleif(2)!" << endl;
          sleep(1);
       }
    }
    
    thread_helper threads;
    
    int main()
    {
       cout << "Erschaffe den einen: " << endl;
       threads.create(&thread1, NULL);
       cout << "Erschaffe den anderen: " << endl;
       threads.create(&thread2, NULL);
    cout << "Und warte geduldig: " << endl;
       threads.join_all();
       return 0;
    }
    

    der code steht unter der GPL, ich hab die lizenzvermerke um platz zu sparen hier entfernt.



  • Es ist wie vermutet so, dass dein Iterator invalid ist. Das liegt daran, dass du in void thread_helper::exit() die pthread_t aus der Liste entfernst. Das sollte aber erst nach dem pthread_join geschehen.



  • wie löse ich das dann am elegantesten? das threads.remove() möchte ich nicht aus exit() entfernen, da ich ja will, dass nach beendigung des threads dieser auch wieder aus der liste entfernt wird.



  • Noch was:

    1. Die Funktion, die an pthread_create übergeben wird, muss C Linkage haben, damit wäre es besser run_call zu ändern: extern "C" void * run_call(....)

    2. Einige Leute argumentieren gegen das sorglose Kopieren von pthread_t. Zum Beispiel http://www.lambdacs.com/cpt/FAQ.html#Q70

    3. Der Aufruf von void thread_helper::run(void *(*func)(void *), void *arg) scheint mir eine Indirektion zuviel zu sein. Das kann man in run_call direkt machen, oder nicht?

    4. Genauso ist der Aufruf von void thread_helper::exit() in run() völlig unnötig.



  • calvin-gr schrieb:

    wie löse ich das dann am elegantesten? das threads.remove() möchte ich nicht aus exit() entfernen, da ich ja will, dass nach beendigung des threads dieser auch wieder aus der liste entfernt wird.

    Die werden doch durch void thread_helper::join_all() aus der Liste entfernt.



  • wenn ich thread_helper::exit() in run() nicht aufrufe, wird die pthread_t ja nicht aus der liste entfernt, wenn sich der thread normal beendet. irgendwann würden also lauter unnötige elemente in meiner liste sein, deren zugehörige threads schon längst nicht mehr existieren. oder denke ich hier irgendwie falsch?



  • calvin-gr schrieb:

    wenn ich thread_helper::exit() in run() nicht aufrufe, wird die pthread_t ja nicht aus der liste entfernt, wenn sich der thread normal beendet. irgendwann würden also lauter unnötige elemente in meiner liste sein, deren zugehörige threads schon längst nicht mehr existieren. oder denke ich hier irgendwie falsch?

    Du musst doch alle Threads wieder joinen. Nach dem join kannst du den Thread ruhig aus der Liste entfernen. Die Alternative ist, dass du den Thread detached machst, dann musst du keinen join machen und kannst den Thread aus der Liste entfernen, wenn er sich beendet. Das könnte dann eventuell per cleanup Handler geschehen.



  • also bleibt der thread, auch wenn er seine arbeit getan hat, bis zum join() bestehen?



  • calvin-gr schrieb:

    also bleibt der thread, auch wenn er seine arbeit getan hat, bis zum join() bestehen?

    Er läuft nicht mehr aber es werden die Daten vorgehalten, die für ein join notwendig sind.

    Aber mal anders gefragt: Was willst du als pthread_t in den join reinstecken, wenn diese bereits aus der Liste entfernt ist?



  • ich habe jetzt einfach die zeile this->exit() in thread_helper::run() auskommentiert. so läuft es.

    vielen dank für deine hilfe


Anmelden zum Antworten