Wie kann ich warten, bis alle pthreads abgeschlossen sind?

Lesezeit: 10 Minuten

Ich möchte nur, dass mein Hauptthread darauf wartet, dass alle meine (p) Threads abgeschlossen sind, bevor er beendet wird.

Die Threads kommen und gehen aus verschiedenen Gründen, und ich möchte sie wirklich nicht alle im Auge behalten – ich möchte nur wissen, wann sie alle weg sind.

wait() tut dies für untergeordnete Prozesse und gibt ECHILD zurück, wenn keine untergeordneten Elemente mehr vorhanden sind, aber wait funktioniert nicht (scheint mit) (p)threads.

Ich möchte mir wirklich nicht die Mühe machen, eine Liste aller ausstehenden Threads zu führen (wie sie kommen und gehen) und dann pthread_join für jeden aufrufen zu müssen.

Gibt es dafür einen Quick-and-Dirty-Weg?

Benutzeravatar von Michael Burr
Michael Burr

Soll Ihr Haupt-Thread etwas Bestimmtes tun, nachdem alle Threads abgeschlossen sind?

Wenn nicht, kannst du deinen Hauptthread einfach aufrufen pthread_exit() Anstatt zurückzukehren (oder anzurufen exit()).

Wenn main() gibt es implizit zurück (oder verhält sich so, als ob es angerufen hätte) exit(), wodurch der Vorgang beendet wird. jedoch, wenn main() Anrufe pthread_exit() Anstatt zurückzukehren, dieser implizite Aufruf an exit() tritt nicht auf und der Prozess wird nicht sofort beendet – er endet, wenn alle Threads beendet wurden.

Zu viel Quick-n-Dirtier geht nicht.

Hier ist ein kleines Beispielprogramm, mit dem Sie den Unterschied sehen können. Passieren -DUSE_PTHREAD_EXIT an den Compiler, um zu sehen, wie der Prozess wartet, bis alle Threads beendet sind. Kompilieren Sie, ohne dass dieses Makro definiert ist, um die Prozessstopp-Threads in ihren Spuren zu sehen.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

static
void sleep(int ms)
{
    struct timespec waittime;

    waittime.tv_sec = (ms / 1000);
    ms = ms % 1000;
    waittime.tv_nsec = ms * 1000 * 1000;

    nanosleep( &waittime, NULL);
}

void* threadfunc( void* c)
{
    int id = (int) c;
    int i = 0;

    for (i = 0 ; i < 12; ++i) {
        printf( "thread %d, iteration %d\n", id, i);
        sleep(10);
    }

    return 0;
}


int main()
{
    int i = 4;

    for (; i; --i) {
        pthread_t* tcb = malloc( sizeof(*tcb));

        pthread_create( tcb, NULL, threadfunc, (void*) i);
    }

    sleep(40);

#ifdef USE_PTHREAD_EXIT
    pthread_exit(0);
#endif

    return 0;
}

  • Danke für die Antwort! Eigentlich – ja – muss der Haupt-Thread ein Shared-Memory-Segment bereinigen/entfernen – also kann ich nicht einfach pthread_exit aufrufen, wie Sie es beschrieben haben. (Mir ist jetzt klar, dass ich dies im OP hätte angeben sollen). Danke für die Antwort!

    – Brad

    31. Mai 2011 um 18:23 Uhr

  • Schöne Antwort. Ich bin ein wenig verwirrt darüber, warum die return Aussage nach der pthread_exit Anweisung beendet den Prozess jedoch nicht. Haben wir jetzt 2 verschiedene Prozesse laufen, wo einer beendet wird und der andere nicht? Ich hatte den (vielleicht irrtümlichen) Eindruck, dass eine Rückkehr vom Main zerstören würde etwas zumindest verarbeiten.

    – Sammaron

    18. Dezember 2014 um 12:05 Uhr

  • @Sammaron: Wenn pthread_exit() heißt, dann die return Erklärung am Ende main() wird nie ausgeführt. Der Thread wird beendet, aber keine der Maschinen zum Abbruch des Prozesses wird ausgeführt. Das Betriebssystem wird dies tun, sobald alle Threads im Prozess beendet wurden (oder ein anderer Thread eine Funktion wie aufrufen kann exit() um den Vorgang zu beenden).

    – Michael Burr

    18. Dezember 2014 um 14:40 Uhr


  • Würde nicht befreien tcbkein Speicherleck verursachen?

    – rasieren

    15. Februar 2016 um 3:37 Uhr

  • @razzak: Ja. Dieses Programm war nur ein einfaches Beispiel, um den Unterschied zwischen Aufrufen zu demonstrieren exit() (oder vermieten main() zurück) und anrufen pthread_exit() aus dem Hauptprogramm. Es soll nicht den vollständigen korrekten Umgang mit Thread-Ressourcen zeigen.

    – Michael Burr

    15. Februar 2016 um 4:28 Uhr


Der richtige Weg ist, alle Ihre pthread_ids im Auge zu behalten, aber Sie haben nach einem schnellen und schmutzigen Weg gefragt, also hier ist es. Grundsätzlich:

  • Behalten Sie einfach die Gesamtzahl der laufenden Threads bei,
  • Erhöhen Sie es in der Hauptschleife, bevor Sie pthread_create aufrufen.
  • Dekrementieren Sie die Thread-Zählung, wenn jeder Thread endet.
  • Schlafen Sie dann am Ende des Hauptprozesses, bis die Zählung auf 0 zurückkehrt.

.

volatile int running_threads = 0;
pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER;

void * threadStart()
{
   // do the thread work
   pthread_mutex_lock(&running_mutex);
   running_threads--;
   pthread_mutex_unlock(&running_mutex);
}

int main()
{
  for (i = 0; i < num_threads;i++)
  {
     pthread_mutex_lock(&running_mutex);
     running_threads++;
     pthread_mutex_unlock(&running_mutex);
     // launch thread

  }

  while (running_threads > 0)
  {
     sleep(1);
  }
}

  • Dies lässt sich viel einfacher mit einer Barriere statt mit Zähler und Mutex bewerkstelligen.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    27. Mai 2011 um 20:11 Uhr

  • Ich mag diese Lösung – es war mir nicht eingefallen, einfach die laufenden Instanzen zu zählen. Ich denke, Sie könnten sogar auf Mutexe verzichten, da die Operationen alle atomar sind, IINM.

    – SlappyTheFish

    27. Mai 2011 um 20:59 Uhr

  • Die Operationen sind definitiv nicht atomar. Der Mutex ist wesentlich. Suchen Sie jedoch nach Barrieren; Sie sind viel einfacher zu verwenden und erledigen das Zählen für Sie.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    27. Mai 2011 um 21:54 Uhr

  • Ich habe gerade nachgesehen, ja, es ist nicht atomar. Interessant – was wäre ein Zwischenzustand einer Inkrementierungsoperation? Teilweise gesetzte/ungesetzte Bits? Wie auch immer, danke für den Hinweis zu Barrieren – ich untersuche sie, sie klingen interessant.

    – SlappyTheFish

    27. Mai 2011 um 22:11 Uhr

  • Ich mag die Lösung nicht – weil es bedeutet, dass ich weiter abfragen und schlafen muss -, aber es ist immer noch die einfachste! In Wirklichkeit würde ich nicht in die While/Sleep-Schleife fallen, bis ich ein Shutdown-Signal erhalten habe – also würde es in der realen Welt nicht wirklich CPU-Zyklen verbrennen.

    – Brad

    31. Mai 2011 um 18:29 Uhr

Wenn Sie Ihre Fäden nicht im Auge behalten möchten, können Sie die Fäden lösen, damit Sie sich nicht um sie kümmern müssen, aber um zu sagen, wann sie fertig sind, müssen Sie etwas weiter gehen.

Ein Trick wäre, eine Liste (verknüpfte Liste, Array, was auch immer) der Status der Threads zu führen. Wenn ein Thread startet, setzt er seinen Status im Array auf etwas wie THREAD_STATUS_RUNNING und kurz bevor er endet, aktualisiert er seinen Status auf etwas wie THREAD_STATUS_STOPPED. Wenn Sie dann überprüfen möchten, ob alle Threads gestoppt wurden, können Sie einfach über dieses Array iterieren und alle Status überprüfen.

Vergessen Sie jedoch nicht, dass Sie in diesem Fall den Zugriff auf das Array so steuern müssen, dass nur ein Thread darauf zugreifen kann (read und schreiben) es auf einmal, also müssen Sie einen Mutex darauf verwenden.

  • Diese Lösung macht die Sache nicht einfacher. Wenn Sie dieses hässliche Array erstellen (das Sie übrigens synchronisieren müssen!), könnten Sie stattdessen einfach die speichern pthread_t ids darin und pthread_join das Einkaufszentrum.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    27. Mai 2011 um 20:14 Uhr

  • Sie haben Recht – aber das OP sagt, dass Threads kommen und gehen und dass er sie nicht verfolgen möchte, was ich so interpretiert habe, dass er nicht allen Threads beitreten möchte, sondern einfach warten kann alle aktuellen Threads beenden, wenn eine Beendigungsbedingung eintritt. Wie Gravitron vorschlägt, wäre das Zählen laufender Threads einfacher und vermeidet die Synchronisierung, aber der Array-Ansatz fügt Flexibilität hinzu (falls erforderlich), sodass jeder Thread durch eine Struktur beschrieben werden kann, die weitere Informationen enthält, z. B. wann er gestartet wurde usw., um vielleicht zu helfen mit Fadenüberwachung.

    – SlappyTheFish

    27. Mai 2011 um 21:49 Uhr

  • Unabhängig davon hat Ihr Ansatz schwerwiegende Fehler. Ein Thread kann seinen eigenen Status nicht auf setzen THREAD_STATUS_RUNNING weil es eine Rennbedingung geben wird, bevor sie eingestellt wird. Stattdessen muss der erstellende Thread dies vor dem Aufrufen tun pthread_create. Es müsste noch viel mehr synchronisiert werden, um Ihren Ansatz gültig zu machen. Wenn Sie kein Experte auf diesem Gebiet sind, pthread_join (oder Barrieren) wäre eine viel einfachere und weniger fehleranfällige Lösung.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    27. Mai 2011 um 21:53 Uhr

  • Absolut – auch hier haben Sie Recht, und Ihre vorgeschlagene Methode zur Vermeidung der Race Condition ist genau richtig. Tatsächlich habe ich es in einem meiner Projekte genau so gelöst. Ich denke, Gravitron oder Michael Burr haben Lösungen, die eher den Wünschen des OP entsprechen, ich gebe nur Denkanstöße 🙂

    – SlappyTheFish

    27. Mai 2011 um 21:57 Uhr

  • Ich kann nicht einfach „detach“ aufrufen – weil ich in meinem Haupt-Thread etwas aufräumen muss, nachdem alle Threads stillgelegt und beendet wurden. Sie haben insofern Recht, als ich auf etwas viel Einfacheres gehofft hatte, als alle Worker-Threads zu verfolgen (zu sperren, zu synchronisieren) und wieder beizutreten. Danke für die Antwort!

    – Brad

    31. Mai 2011 um 18:26 Uhr

Benutzeravatar von Nick Sotiros
Nick Sotiros

Sie könnten eine Liste aller Ihrer Thread-IDs führen und dann pthread_join für jede ausführen. Natürlich benötigen Sie einen Mutex, um den Zugriff auf die Thread-ID-Liste zu steuern. Sie benötigen auch eine Art Liste, die während der Iteration geändert werden kann, vielleicht ein std::set?

int main() {
   pthread_mutex_lock(&mutex);

   void *data;
   for(threadId in threadIdList) {
      pthread_mutex_unlock(&mutex);
      pthread_join(threadId, &data);
      pthread_mutex_lock(&mutex);
   }

   printf("All threads completed.\n");
}

// called by any thread to create another
void CreateThread()
{
   pthread_t id;

   pthread_mutex_lock(&mutex);
   pthread_create(&id, NULL, ThreadInit, &id); // pass the id so the thread can use it with to remove itself
   threadIdList.add(id);
   pthread_mutex_unlock(&mutex);  
}

// called by each thread before it dies
void RemoveThread(pthread_t& id)
{
   pthread_mutex_lock(&mutex);
   threadIdList.remove(id);
   pthread_mutex_unlock(&mutex);
}

Danke an alle für die tollen Antworten! Es wurde viel über die Verwendung von Speicherbarrieren usw. gesprochen – also dachte ich, ich würde eine Antwort posten, die richtig zeigt, dass sie dafür verwendet werden.

#define NUM_THREADS 5

unsigned int thread_count;
void *threadfunc(void *arg) {
  printf("Thread %p running\n",arg);
  sleep(3);
  printf("Thread %p exiting\n",arg);
  __sync_fetch_and_sub(&thread_count,1);
  return 0L;
}

int main() {
  int i;
  pthread_t thread[NUM_THREADS];

  thread_count=NUM_THREADS;
  for (i=0;i<NUM_THREADS;i++) {
    pthread_create(&thread[i],0L,threadfunc,&thread[i]);
  }

  do {
    __sync_synchronize();
  } while (thread_count);
  printf("All threads done\n");
}

Beachten Sie, dass die __sync-Makros “nicht standardmäßige” GCC-interne Makros sind. LLVM unterstützt diese auch – aber wenn Sie einen anderen Compiler verwenden, müssen Sie möglicherweise etwas anderes tun.

Eine weitere wichtige Sache, die Sie beachten sollten, ist: Warum sollten Sie einen ganzen Kern verbrennen oder “die Hälfte” einer CPU verschwenden, die sich in einer engen Abfrageschleife dreht und nur darauf wartet, dass andere fertig werden – wenn Sie es einfach zum Laufen bringen könnten? Der folgende Mod verwendet den anfänglichen Thread, um einen der Worker auszuführen, und wartet dann, bis die anderen fertig sind:

  thread_count=NUM_THREADS;
  for (i=1;i<NUM_THREADS;i++) {
    pthread_create(&thread[i],0L,threadfunc,&thread[i]);
  }

  threadfunc(&thread[0]);

  do {
    __sync_synchronize();
  } while (thread_count);
  printf("All threads done\n");
}

Beachten Sie, dass wir beginnen, die Threads bei „1“ statt bei „0“ zu erstellen, dann direkt „thread 0“ inline ausführen und darauf warten, dass alle Threads abgeschlossen werden, nachdem es fertig ist. Wir übergeben &thread[0] aus Gründen der Konsistenz (auch wenn es hier bedeutungslos ist), obwohl Sie in Wirklichkeit wahrscheinlich Ihre eigenen Variablen/Kontext übergeben würden.

1389260cookie-checkWie kann ich warten, bis alle pthreads abgeschlossen sind?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy