Wiederverwenden von Thread in Schleife c ++

7

Ich muss einige Aufgaben in einem C ++ - Programm parallelisieren und bin völlig neu in der parallelen Programmierung. Ich habe bis jetzt einige Fortschritte bei der Suche im Internet gemacht, bin aber jetzt ein bisschen festgefahren. Ich möchte einige Threads in einer Schleife wiederverwenden, aber ich weiß eindeutig nicht, wie ich das tun soll, was ich versuche.

Ich erlange Daten von zwei ADC-Karten auf dem Computer (parallel erfasst), dann muss ich einige Operationen an den gesammelten Daten (parallel verarbeitet) durchführen, während ich den nächsten Datenstapel sammle. Hier ist ein Pseudocode zur Veranschaulichung

%Vor%

Das ist das Wesentliche davon. Der nächste Durchlauf der Schleife würde in die "a" -Adressen schreiben, während die "b" -Daten verarbeitet werden und weiter alternieren (ich kann den Code dazu bekommen, er nahm ihn einfach heraus, um das Problem nicht zu überlasten).

Wie auch immer, das Problem (wie ich sicher bin, dass einige Leute es bereits wissen) ist, dass der Compiler (VS2012) das zweite Mal, wenn ich versuche, acq1 und acq2 zu verwenden, "IntelliSense: Aufruf eines Objekts einer Klassenart ohne geeignete operator () - oder Konvertierungsfunktionen zu pointer-to-function-Typ ". Ebenso, wenn ich std :: thread wieder vor acq1 und acq2 setze, heißt es "error C2374: 'acq1': redefinition; multiple initialization".

Die Frage ist also, ob ich Threads einer neuen Aufgabe zuweisen kann, wenn sie ihre vorherige Aufgabe erledigt haben? Ich warte immer darauf, dass die vorherige Verwendung des Threads beendet wird, bevor ich ihn erneut aufruft, aber ich weiß nicht, wie ich den Thread neu zuweisen soll, und da er in einer Schleife ist, kann ich nicht jedes Mal einen neuen Thread erstellen (oder wenn ich könnte, das scheint verschwenderisch und unnötig, aber ich könnte mich irren).

Vielen Dank im Voraus

    
notaCSmajor 22.10.2014, 20:39
quelle

6 Antworten

15

Der einfachste Weg ist die Verwendung einer Warteschlange für std::function -Objekte. So:

%Vor%     
David Schwartz 20.04.2015 08:20
quelle
9

Die Klasse std::thread wurde entwickelt, um genau eine Aufgabe (diejenige, die Sie im Konstruktor angeben) auszuführen und dann zu beenden. Wenn Sie mehr arbeiten möchten, benötigen Sie einen neuen Thread. Ab C ++ 11 ist das alles, was wir haben. Thread-Pools haben es nicht in den Standard geschafft. (Ich bin unsicher, was C ++ 14 über sie zu sagen hat.)

Glücklicherweise können Sie die erforderliche Logik selbst implementieren. Hier ist das großformatige Bild:

  • Starten Sie n Worker-Threads, die alle Folgendes ausführen:
    • Wiederholen Sie dies, während Sie noch mehr zu tun haben:
      • Besorgen Sie sich die nächste Aufgabe t (möglicherweise warten Sie, bis einer bereit ist).
      • Verarbeiten Sie t .
  • Fügen Sie neue Aufgaben in die Verarbeitungswarteschlange ein.
  • Sagen Sie den Arbeitsthreads, dass nichts mehr zu tun ist.
  • Warten Sie, bis die Worker-Threads beendet sind.

Der schwierigste Teil hier (der immer noch ziemlich einfach ist) ist die richtige Gestaltung der Arbeitswarteschlange. Normalerweise wird dafür eine synchronisierte verkettete Liste (aus der STL) benötigt. Synchronisiert bedeutet, dass jeder Thread, der die Warteschlange manipulieren möchte, dies erst tun muss, nachdem er ein std::mutex erworben hat, um Race-Bedingungen zu vermeiden. Wenn ein Worker-Thread die Liste leer findet, muss er warten, bis wieder etwas Arbeit ist. Sie können dafür ein std::condition_variable verwenden. Jedes Mal, wenn eine neue Aufgabe in die Warteschlange eingefügt wird, benachrichtigt der Einfügethread einen Thread, der auf die Bedingungsvariable wartet und daher die Blockierung stoppt und eventuell mit der Verarbeitung der neuen Aufgabe beginnt.

Der zweite nicht so triviale Teil besteht darin, den Worker-Threads zu signalisieren, dass es keine Arbeit mehr zu tun gibt. Natürlich können Sie einige globale Flags setzen, aber wenn ein Arbeiter blockiert ist, der in der Warteschlange wartet, wird er es in nächster Zeit nicht merken. Eine Lösung könnte sein, notify_all() threads zu verwenden und sie die Markierung bei jeder Benachrichtigung überprüfen zu lassen. Eine andere Möglichkeit besteht darin, einen bestimmten "toxischen" Gegenstand in die Warteschlange einzufügen. Wenn ein Arbeiter diesen Gegenstand entdeckt, wird er selbst beendet.

Das Darstellen einer Warteschlange von Aufgaben ist einfach, indem Sie Ihre selbstdefinierten task -Objekte oder einfach Lambda-Zeichen verwenden.

Alle oben genannten Funktionen sind C ++ 11. Wenn Sie mit einer früheren Version nicht weiterkommen, müssen Sie auf Bibliotheken von Drittanbietern zurückgreifen, die Multi-Threading für Ihre spezielle Plattform bereitstellen.

Obwohl nichts davon Raketenwissenschaft ist, ist es immer noch leicht, das erste Mal falsch zu verstehen. Und leider gehören Fehler im Zusammenhang mit dem gemeinsamen Zugriff zu den am schwierigsten zu debuggenden. Es kann sich schnell auszahlen, wenn man ein paar Stunden damit verbringt, die relevanten Abschnitte eines guten Buches durchzulesen oder ein Tutorial zu bearbeiten.

    
5gon12eder 22.10.2014 21:30
quelle
0

Dies

%Vor%

ist der Aufruf eines Konstruktors. Konstruieren eines neuen Objekts namens acq1

Dies

%Vor%

ist die Anwendung des Operators () auf das vorhandene Objekt aqc1. Wenn für std :: thread kein solcher Operator definiert ist, beschwert sich der Compiler.

Soweit ich weiß, dürfen Sie std :: threads nicht wiederverwenden. Du konstruierst und startest sie. Schließe dich ihnen an und wirf sie weg,

    
Oncaphillis 22.10.2014 21:08
quelle
0

Nun, es kommt darauf an, ob Sie eine Neuzuweisung verschieben oder nicht. Sie können einen Thread verschieben, aber keine Kopie davon erstellen.

Der folgende Code erstellt bei jeder Iteration ein neues Paar von Threads und verschiebt sie anstelle von alten Threads. Ich denke, das sollte funktionieren, weil neue thread -Objekte Provisorien sein werden.

%Vor%

Was passiert, ist, dass das Objekt seine Lebenszeit am Ende der Iteration nicht beendet, weil es in Bezug auf die Schleife im äußeren Bereich deklariert ist. Aber jedes Mal wird ein neues Objekt erstellt und move findet statt. Ich sehe nicht, was geschont werden kann (ich könnte dumm sein), also stelle ich mir vor, dass es genau dasselbe ist, wenn acq s innerhalb der Schleife deklariert wird und einfach das Symbol wiederverwendet wird. Alles in allem ... ja, es geht darum, wie Sie ein create temporary und move klassifizieren.

Auch das startet eindeutig einen neuen Thread für jede Schleife (natürlich endet der zuvor zugewiesene Thread), es lässt einen Thread nicht auf neue Daten warten und führt ihn magisch dem Verarbeitungsrohr zu. Du müsstest es anders umsetzen. Beispiel: Worker-Thread-Pool und Kommunikation über Warteschlangen.

Referenzen: operator= , (ctor) .

Ich denke, dass die Fehler, die Sie bekommen, selbsterklärend sind, also werde ich sie nicht erklären.

    
luk32 22.10.2014 21:17
quelle
0

Ich denke, dass Sie eine viel einfachere Antwort benötigen, um eine Reihe von Threads mehr als einmal auszuführen, das ist die beste Lösung:

%Vor%     
Maziar Sedghi 09.06.2017 23:47
quelle
0

Sie könnten auch Ihre eigene Thread-Klasse erstellen und ihre run-Methode wie folgt aufrufen:

%Vor%     
Christoph 28.12.2017 19:25
quelle