C++20: Kooperative Unterbrechung eines Thread mit Callbacks
![Viele Eisenbahnschienen vor Sonnenuntergang](https://heise.cloudimg.io/width/700/q75.png-lossy-75.webp-lossy-75.foil1/_www-heise-de_/imgs/18/4/6/3/2/8/3/5/shutterstock_124099447-b5b29f41aa95c9d4.jpeg)
(Bild: Patrick Poendl/Shutterstock.com)
Nach der grundsätzlichen Vorstellung zum kooperativen Unterbrechen eines Thread geht dieser Beitrag auf die Details ein.
Zur Erinnerung: In meinem vorherigen Artikel "Softwareentwicklung: Kooperatives Unterbrechen eines Threads in C++20 [1]" habe ich das folgende Programm vorgestellt.
![](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/6/3/2/8/3/5/TimelineCpp20Interruption-824aebb896427f49.png)
// invokeCallback.cpp
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>
using namespace::std::literals;
auto func = [](std::stop_token stoken) { // (1)
int counter{0};
auto thread_id = std::this_thread::get_id();
std::stop_callback callBack(stoken,
[&counter, thread_id] { // (2)
std::cout << "Thread id: " << thread_id
<< "; counter: " << counter << '\n';
});
while (counter < 10) {
std::this_thread::sleep_for(0.2s);
++counter;
}
};
int main() {
std::cout << '\n';
std::vector<std::jthread> vecThreads(10);
for(auto& thr: vecThreads) thr = std::jthread(func);
std::this_thread::sleep_for(1s); // (3)
for(auto& thr: vecThreads) thr.request_stop(); // (4)
std::cout << '\n';
}
Jeder der zehn Threads ruft die Lambda-Funktion func
(1) auf. Der Callback (2) zeigt die Thread-ID und den Zähler an. Da der Haupt-Thread (3) eine Sekunde schläft und die untergeordneten Threads schlafen, ist der Zähler 4, wenn die Callbacks aufgerufen werden. Der Aufruf thr.request_stop()
löst den Callback auf jedem Thread aus.
![](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/6/3/2/8/3/5/invokeCallback-7b9122c69d3d87db.png)
Eine Frage wurde in meinem letzten Artikel nicht beantwortet:
Wo wird der Callback ausgeführt?
Der std::stop_callback
-Konstruktor registriert die Callback-Funktion für das std::stop_token
, das durch die zugehörige std::stop_source
geben ist. Diese Callback-Funktion wird entweder in dem Thread aufgerufen, der request_stop()
aufruft, oder in dem Thread, der den std::stop_callback
konstruiert. Wenn die Aufforderung zum Anhalten vor der Registrierung des std::stop_callback
erfolgt, wird der Callback in dem Thread aufgerufen, der den std::stop_callback
konstruiert. Andernfalls wird der Callback in dem Thread aufgerufen, der request_stop
aufruft. Erfolgt der Aufruf request_stop()
nach der Ausführung des Threads, der den std::stop_callback
konstruiert, wird der registrierte Callback nie aufgerufen.
Man kann mehr als einen Callback für einen oder mehrere Threads mit demselben std::stop_token
registrieren. Der C++-Standard bietet keine Garantie für die Reihenfolge, in der sie ausgeführt werden.
Mehr als ein Callback
// invokeCallbacks.cpp
#include <chrono>
#include <iostream>
#include <thread>
using namespace std::literals;
void func(std::stop_token stopToken) {
std::this_thread::sleep_for(100ms);
for (int i = 0; i <= 9; ++i) {
std::stop_callback cb(stopToken, [i] { std::cout << i; });
}
std::cout << '\n';
}
int main() {
std::cout << '\n';
std::jthread thr1 = std::jthread(func);
std::jthread thr2 = std::jthread(func);
thr1.request_stop();
thr2.request_stop();
std::cout << '\n';
}
![](https://heise.cloudimg.io/width/400/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/4/6/3/2/8/3/5/invokeCallbacks-89818f7c9e529609.png)
Ein allgemeiner Mechanismus zum Senden von Signalen
Das Paar std::stop_source
und std::stop_token
kann als allgemeiner Mechanismus zum Senden eines Signals betrachtet werden. Indem man das std::stop_token
kopiert, kann man das Signal an jede Entität senden, die etwas ausführt. Im folgenden Beispiel verwende ich std::async, std::promise, std::thread
und std::jthread
in verschiedenen Kombinationen.
// signalStopRequests.cpp
#include <iostream>
#include <thread>
#include <future>
using namespace std::literals;
void function1(std::stop_token stopToken, const std::string& str){
std::this_thread::sleep_for(1s);
if (stopToken.stop_requested()) std::cout << str
<< ": Stop requested\n";
}
void function2(std::promise<void> prom,
std::stop_token stopToken, const std::string& str) {
std::this_thread::sleep_for(1s);
std::stop_callback callBack(stopToken, [&str] {
std::cout << str << ": Stop requested\n";
});
prom.set_value();
}
int main() {
std::cout << '\n';
std::stop_source stopSource; // (1)
std::stop_token stopToken =
std::stop_token(stopSource.get_token()); // (2)
std::thread thr1 =
std::thread(function1, stopToken, "std::thread"); // (3)
std::jthread jthr =
std::jthread(function1, stopToken, "std::jthread"); // (4)
auto fut1 = std::async([stopToken] { // (5)
std::this_thread::sleep_for(1s);
if (stopToken.stop_requested()) std::cout
<< "std::async: Stop requested\n";
});
std::promise<void> prom; // (6)
auto fut2 = prom.get_future();
std::thread thr2(function2, std::move(prom),
stopToken, "std::promise");
stopSource.request_stop(); // (7)
if (stopToken.stop_requested())
std::cout << "main: Stop requested\n"; // (8)
thr1.join();
thr2.join();
std::cout << '\n';
}
Dank der stopSource
(1) kann ich das stopToken
(2) für jede laufende Entität verwenden, z. B. std::thread
(3), std::jthread (4), std::async (5) oder std::promise
(6). Ein std::stop_token
ist billig zu kopieren. (7) löst stopSource.request_stop
aus. Außerdem erhält der Haupt-Thread (8) das Signal. Ich verwende in diesem Beispiel std::jthread. std::jthread
und std::condition_variable_any
haben explizite Mitgliedsfunktionen, um mit kooperativen Unterbrechungen bequemer umgehen zu können. Mehr steht im Artikel "Ein verbesserter Thread mit C++20 [2]".
Wie geht's weiter?
In den nächsten zwei Wochen werde ich eine Schreibpause einlegen. Danach werde ich wieder in C++23 einsteigen und das erste Mal in C++26 nachlegen. (rme [3])
URL dieses Artikels:
https://www.heise.de/-9799549
Links in diesem Artikel:
[1] https://www.heise.de/blog/Softwareentwicklung-Kooperatives-Unterbrechen-eines-Threads-in-C-20-9784410.html
[2] https://www.heise.de/blog/Ein-verbesserter-Thread-mit-C-20-5054205.html
[3] mailto:rme@ix.de
Copyright © 2024 Heise Medien