std::future in Klasse noch Threadsafe?
-
Hallo an alle,
ich habe einen Workerthread, der für bestimmte Aufgaben Parameter verwendet. Zur überprüfung ob eine Aufgabe bereits fertig ist, wollte ich ein std::packeged_task bzw. ein std::future verwenden. Soweit gibt es keine Probleme. Da ich im Falle einer Ausnahme im Workerthread die Behandlung im Mainthread machen möchte, eignet sich std::future::get() natürlich hervorragend. Jedoch möchte ich bei einer Ausnahme gerne eine Fehlermeldung passend zu den verwendeten Parametern generieren. Die Idee war, in der Parameterklasse das Rückgabe-future zu speichern. Ich bin mir jetzt jedoch nicht sicher ob das nicht u.U. Threadunsafe ist.
Ich habe mal ein Pseudobeispiel erstellt, damit das Problem verständlicher ist:#include<functional> #include<thread> #include<queue> #include<mutex> #include<condition_variable> #include<future> #include<memory> #include<iostream> #include<string> using namespace std; queue<function<void()>> tasks; mutex m; condition_variable cv; bool shutdown{ false }; void worker_function() { function<void()> task; while( true ) { { unique_lock<mutex> lock( m ); while( tasks.empty() && !shutdown ) cv.wait( lock ); if( shutdown ) return; task = move( tasks.front() ); tasks.pop(); } task(); task = nullptr; } } // Ein simples Data-Objekt als Beispiel struct objekt { string data; // Parameter std::future<long long> result; // Ergebnis }; long long bsp_task( const shared_ptr<objekt>& bsp_data ) { return stoll( bsp_data->data ); } int main() { thread worker{ worker_function }; shared_ptr<objekt> shared_data{ make_shared<objekt>() }; shared_data->data = "42"; auto ptask{ make_shared<std::packaged_task<long long()>>( bind( bsp_task , cref( shared_data ) ) ) }; shared_data->result = ptask->get_future(); { lock_guard<mutex> lock( m ); tasks.emplace( [ptask] { ( *ptask )(); } ); } cv.notify_one(); // Hier ist der eigentliche Knackpunkt: // Ist der Aufruf von result.get() noch threadsafe, obwohl u.U. auf den data-Member zugegriffen wird (in anderen Beispielen evtl. auch schreibend) cout << shared_data->result.get() << endl; cin.get(); { lock_guard<mutex> lock( m ); shutdown = true; } cv.notify_one(); worker.join(); return 0; }
-
Ich hoffe ich habe dich jetzt richtig verstanden und versuche mich mal an einer Beantwortung.
Der Aufruf von shared_data->result.get() ist sicher, da der Rückgabetyp long long kopiert wird.
Bei einem Referenztyp als Shared State wäre die Sache kritischer.
Generell:
Sobald du auf shared_data->data schreibend in einem weiteren Thread zugreifst hast du einen data race.Also:
... cv.notify_one(); //der Workerthread greift lesend auf shared_data->data zu //der Mainthread greift schreibend auf shared_data->data zu // -> Potentieller data race shared_data->data = "55"; cout << shared_data->result.get() << endl; ...
Wenn du wartest bis der WorkerThread fertig ist und dann auf shared_data->data schreibend zugreifst ist alles gut:
... cv.notify_one(); cout << shared_data->result.get() << endl; //der Workerthread ist mit der Bearbeitung des Tasks fertig. //der Mainthread kann sicher auf shared_data->data zugreifen. shared_data->data = "55"; ...