Antworten und Lösungsvorschlag
Antworten zu den einzelnen Fragen:
-
Die capture im Konstruktor erfasst this. Das ist der Zeiger auf das eigene Objekt. Dieser Zeiger ist notwendig, um den Aufruf von process_jobs zu ermöglichen. Da es sich dabei um einen Zeiger handelt, ist eine capture mit einer Zuweisung hierfür ausreichend. Eine Referenz wäre auch zulässig.
-
Der Thread darf erst starten, wenn auch mutex und cv initialisiert sind. Die Reihenfolge der Initialisierung hängt davon ab, in welcher Reihenfolge die einzelnen Komponenten deklariert sind. In der Vorlage war der Thread t zuerst deklariert und würde somit vor dem mutex und dem cv-Objekt initialisiert werden. Natürlich ließe sich die Reihenfolge der Deklarationen anpassen. Im Sinne einer defensiven Programmierung erscheint es hier aber geschickter, den Start des Threads auf einen Zeitpunkt zu verschieben, zu dem garantiert alle Komponenten initialisiert sind -- unabhängig von der Reihenfolge der Deklarationen. Für viele wäre es doch sehr überraschend, wenn ein potentieller Crash eines Programms nur von der Reihenfolge der Deklarationen abhängen würde. Und Warnungen würde es hier nicht geben.
-
Das Programm terminierte mit einem Aufruf von std::terminate, weil das Objekt worker dekonstruiert wurde, obwohl dessen Thread worker.t noch lief. Dies ist nicht zulässig. Somit muss der Destruktor
-
sich mit der Beendigung des Threads mit t.join() synchronisieren und
-
dann natürlich auch dafür sorgen, dass der Thread bei passender Gelegenheit terminiert.
Die passende Gelegenheit erscheint hier das Abarbeiten der restlichen Jobs.
-
Die folgende Lösung sieht die zusätzliche Variable finished vor, die auf true gesetzt wird, sobald der Destruktor beginnt. Danach erfolgt eine Notifikation (der Thread könnte ja gerade wegen einer leeren Job-Queue warten) und schließlich der Aufruf von t.join. Die Methode process_jobs wurde erweitert, so dass auf das Setzen von finished reagiert wird, d.h. die Schleife wird verlassen, sobald alle verbleibenden Jobs abgearbeitet sind und finished gesetzt ist:
#include <cassert> #include <condition_variable> #include <cstdio> #include <functional> #include <list> #include <mutex> #include <thread> #include <utility> struct Worker { public: using Job = std::function<void()>; Worker() : finished(false) { t = std::thread([=]() { process_jobs(); }); } ~Worker() { { std::unique_lock<std::mutex> lock(mutex); finished = true; } cv.notify_one(); t.join(); } void submit(Job job) { std::unique_lock<std::mutex> lock(mutex); jobs.push_back(std::move(job)); cv.notify_one(); } private: bool finished; std::thread t; std::mutex mutex; std::condition_variable cv; std::list<Job> jobs; void process_jobs() { for(;;) { Job job; /* fetch job */ { std::unique_lock<std::mutex> lock(mutex); if (jobs.empty() && !finished) { cv.wait(lock); } if (jobs.empty() && finished) break; job = std::move(jobs.front()); jobs.pop_front(); } /* execute job */ job(); } } }; int main() { Worker worker; worker.submit([]() { std::printf("Hi, this is your first job!\n"); }); worker.submit([]() { std::printf("Now you get another job.\n"); }); }