上一章介绍了新线程库jthread的简单使用以及和thread库的区别,此章继续介绍和jthread相关并发新特性,stop_token、stop_source、stop_callback。这些方法提供给线程之间同步停止状态方式。
- jthread类提供了三个接口来处理停止token,如下。
get_stop_source // 获取和本线程停止状态关联的stop_resource
get_stop_token // 获取和本线程停止状态关联的stop_token
request_stop // 请求修改停止状态为停止
jthread中使用上面上个接口的示例如下:
#include <thread>
#include <iostream>
#include <chrono>
int main(int argc, char const *argv[])
{
auto f = [] (std::stop_token st) {
for (size_t i = 0; i < 10 && !st.stop_requested(); i++) { // 通过st获取停止状态
std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
};
std::jthread t0(f);
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
t0.request_stop(); // 等价于 t0.get_stop_source().request_stop();
return 0;
}
对与例子中的f函数,第一个参数是std::stop_token,这个参数可以不用传,jthread调用的时候会把与自身停止状态关联的stop_token传进去,当我们调用线程的request_stop()函数后就可以通过次stop_token获取到是否已被请求停止。我们也可以自己传stop_token进去,可以自定义stop_resource并且获取stop_token。
- stop_resource
stop_source 类提供了发出停止请求的方法,例如用于 std::jthread 取消。针对一个 stop_source 对象发出的停止请求,对于所有具有相同关联停止状态的 stop_sources 和 std::stop_tokens 都是可见的;任何注册到关联 std::stop_token(s)的 std::stop_callback(s)都会被调用,并且任何等待关联 std::stop_token(s)的 std::condition_variable_any 对象都会被唤醒。一旦发出停止请求,就无法撤回。额外的停止请求没有效果。
#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals; // 时间字面量,在literals文章中有介绍
void worker_fun(int id, std::stop_source stop_source){
std::stop_token stoken = stop_source.get_token();
for (int i = 10; i; --i) {
std::this_thread::sleep_for(300ms);
if (stoken.stop_requested()) {
std::printf(" worker%d is requested to stop\n", id);
return;
}
std::printf(" worker%d goes back to sleep\n", id);
}
}
int main() {
std::jthread threads[4];
std::cout << std::boolalpha;
auto print = [](const std::stop_source& source) {
std::printf("stop_source stop_possible = %s, stop_requested = %s\n",
source.stop_possible() ? "true" : "false",
source.stop_requested() ? "true" : "false");
};
// Common source
std::stop_source stop_source;
print(stop_source);
// Create worker threads
for (int i = 0; i < 4; ++i)
threads[i] = std::jthread(worker_fun, i + 1, stop_source);
std::this_thread::sleep_for(500ms);
std::puts("Request stop");
stop_source.request_stop();
print(stop_source);
// Note: destructor of jthreads will call join so no need for explicit calls
}
- stop_token
stop_token 类提供了检查针对其关联的 std::stop_source 对象是否已经发出或可以发出停止请求的方法,它本质上是关联停止状态的线程安全“视图”。
stop_token 也可以传递给 std::stop_callback 的构造函数,以便在 stop_token 关联的 std::stop_source 被请求停止时调用回调。并且 stop_token 可以传递给 std::condition_variable_any 的可中断等待函数,以在请求停止时中断条件变量的等待。
如下示例:
#include <iostream>
#include <thread>
using namespace std::literals::chrono_literals;
void f(std::stop_token stop_token, int value) {
while (!stop_token.stop_requested()) {
std::cout << value++ << ' ' << std::flush;
std::this_thread::sleep_for(200ms);
}
std::cout << std::endl;
}
int main() {
std::jthread thread(f, 5); // 可以不用传stop_token参数,jthread自动填充
std::this_thread::sleep_for(3s);
}
- stop_callback
stop_callback 类模板提供了一个 RAII 对象类型,用于注册关联的 std::stop_token 对象的回调函数,以便在请求停止关联的 std::stop_source 时调用回调函数。
通过 stop_callback 的构造函数注册的回调函数,将在成功调用 stop_callback 关联的 std::stop_token 的 std::stop_source 的 request_stop() 的同一线程中被调用;或者如果在构造函数注册之前已经请求停止,则回调将在构造 stop_callback 的线程中被调用。
可以为同一个 std::stop_token 创建多个 stop_callback,它们可以来自同一个或不同的线程,并且可以同时创建。不能保证它们执行的顺序,但它们将被同步调用;除非 stop_callback(s)是在已经为 std::stop_token 请求停止之后构造的,如前所述。
如果回调的调用通过异常退出,则调用 std::terminate。std::stop_callback 不可复制构造、复制赋值、移动构造或移动赋值。模板参数 Callback 类型必须既是可调用的又是可销毁的。任何返回值都将被忽略。
示例代码如下:
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
using namespace std::chrono_literals;
// 打印帮助类,帮助缓存所有日志,在被析构时打印所有日志.
class Writer {
std::ostringstream buffer;
public:
~Writer() { std::cout << buffer.str(); }
Writer& operator<<(auto input) { buffer << input; return *this; }
};
int main() {
std::jthread worker([] (std::stop_token stoken) {
Writer() << "Worker thread's id: " << std::this_thread::get_id() << '\n';
std::mutex mutex;
std::unique_lock lock(mutex);
std::condition_variable_any().wait(lock, stoken,
[&stoken] { return stoken.stop_requested(); }); // 这里当调用和stoken关联的stop_resource被调用request_stop()时,会唤醒线程,然后判断退出条件
});
// 注册回调函数
std::stop_callback callback(worker.get_stop_token(), [] {
Writer() << "Stop callback executed by thread: "
<< std::this_thread::get_id() << '\n';
});
auto stopper_func = [&worker] {
if (worker.request_stop()) // 其中一个线程会调用成功
Writer() << "Stop request executed by thread: "
<< std::this_thread::get_id() << '\n';
else
Writer() << "Stop request not executed by thread: "
<< std::this_thread::get_id() << '\n';
};
// 多个线程请求停止worker线程.
std::jthread stopper1(stopper_func);
std::jthread stopper2(stopper_func);
stopper1.join();
stopper2.join();
// worker线程已经被调用了request_stop(), 主线程注册回调函数会立即被执行。
Writer() << "Main thread: " << std::this_thread::get_id() << '\n';
std::stop_callback callback_after_stop(worker.get_stop_token(), [] {
Writer() << "Stop callback executed by thread: "
<< std::this_thread::get_id() << '\n';
});
}
C++20并发库提供了更加方便的使用多线程编程的库,大家可以多多了解使用,后续会介绍更多并发编程的特性。