From 56b78956a003b91e538cd5c680d614fdaee9c9eb Mon Sep 17 00:00:00 2001 From: Thomas Rodgers <trodgers@trodgers.remote.f30> Date: Wed, 23 Oct 2019 12:32:31 -0700 Subject: [PATCH] Add C++20 jthread type to <thread>
--- libstdc++-v3/ChangeLog | 8 + libstdc++-v3/include/std/stop_token | 14 ++ libstdc++-v3/include/std/thread | 125 +++++++++++ .../testsuite/30_threads/jthread/1.cc | 27 +++ .../testsuite/30_threads/jthread/2.cc | 27 +++ .../testsuite/30_threads/jthread/jthread.cc | 198 ++++++++++++++++++ 6 files changed, 399 insertions(+) create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/1.cc create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/2.cc create mode 100644 libstdc++-v3/testsuite/30_threads/jthread/jthread.cc diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 970c5c2a018..523620da1c3 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,11 @@ +2019-10-23 Thomas Rodgers <trodg...@redhat.com> + + * include/std/stop_token (stop_token): Add operator==(), operator!=(). + * include/std/thread: Add jthread type. + * testsuite/30_threads/jthread/1.cc: New test. + * testsuite/30_threads/jthread/2.cc: New test. + * testsuite/30_threads/jthread/jthread.cc: New test. + 2019-10-22 Thomas Rodgers <trodg...@redhat.com> * include/Makefile.am: Add <stop_token> header. diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token index b3655b85eae..04b9521d24e 100644 --- a/libstdc++-v3/include/std/stop_token +++ b/libstdc++-v3/include/std/stop_token @@ -87,6 +87,20 @@ namespace std _GLIBCXX_VISIBILITY(default) return stop_possible() && _M_state->_M_stop_requested(); } + [[nodiscard]] + friend bool + operator==(const stop_token& __a, const stop_token& __b) + { + return __a._M_state == __b._M_state; + } + + [[nodiscard]] + friend bool + operator!=(const stop_token& __a, const stop_token& __b) + { + return __a._M_state == __b._M_state; + } + private: friend stop_source; template<typename _Callback> diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread index 90b4be6cd16..93afa766d18 100644 --- a/libstdc++-v3/include/std/thread +++ b/libstdc++-v3/include/std/thread @@ -39,6 +39,13 @@ #include <memory> #include <tuple> #include <cerrno> + +#if __cplusplus > 201703L +#define __cpp_lib_jthread 201907L +#include <functional> +#include <stop_token> +#endif + #include <bits/functexcept.h> #include <bits/functional_hash.h> #include <bits/invoke.h> @@ -409,6 +416,124 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // @} group threads +#ifdef __cpp_lib_jthread + + class jthread + { + public: + using id = std::thread::id; + using native_handle_type = std::thread::native_handle_type; + + jthread() noexcept + : _M_stop_source{ nostopstate_t{ } } + { } + + template<typename _Callable, typename... _Args, + typename = std::enable_if_t<!std::is_same_v<std::decay_t<_Callable>, jthread>>> + explicit + jthread(_Callable&& __f, _Args&&... __args) + : _M_thread{[](stop_token __token, auto&& __cb, auto&&... __args) + { + if constexpr(std::is_invocable_v<_Callable, stop_token, _Args...>) + { + std::invoke(std::forward<decltype(__cb)>(__cb), + std::move(__token), + std::forward<decltype(__args)>(__args)...); + } + else + { + std::invoke(std::forward<decltype(__cb)>(__cb), + std::forward<decltype(__args)>(__args)...); + } + }, + _M_stop_source.get_token(), + std::forward<_Callable>(__f), + std::forward<_Args>(__args)...} + { } + + jthread(const jthread&) = delete; + jthread(jthread&&) noexcept = default; + + ~jthread() + { + if (joinable()) + { + request_stop(); + join(); + } + } + + jthread& + operator=(const jthread&) = delete; + + jthread& + operator=(jthread&&) noexcept = default; + + void + swap(jthread& __other) noexcept + { + std::swap(_M_stop_source, __other._M_stop_source); + std::swap(_M_thread, __other._M_thread); + } + + bool + joinable() const noexcept + { + return _M_thread.joinable(); + } + + void + join() + { + _M_thread.join(); + } + + void + detach() + { + _M_thread.detach(); + } + + id + get_id() const noexcept + { + _M_thread.get_id(); + } + + native_handle_type + native_handle() + { + return _M_thread.native_handle(); + } + + static unsigned + hardware_concurrency() noexcept + { + return std::thread::hardware_concurrency(); + } + + [[nodiscard]] stop_source + get_stop_source() noexcept + { + return _M_stop_source; + } + + [[nodiscard]] stop_token + get_stop_token() const noexcept + { + return _M_stop_source.get_token(); + } + + bool request_stop() noexcept + { + return get_stop_source().request_stop(); + } + + private: + stop_source _M_stop_source; + std::thread _M_thread; + }; +#endif // __cpp_lib_jthread _GLIBCXX_END_NAMESPACE_VERSION } // namespace diff --git a/libstdc++-v3/testsuite/30_threads/jthread/1.cc b/libstdc++-v3/testsuite/30_threads/jthread/1.cc new file mode 100644 index 00000000000..1fb5650dbc6 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/1.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <thread> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <thread>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <thread>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/jthread/2.cc b/libstdc++-v3/testsuite/30_threads/jthread/2.cc new file mode 100644 index 00000000000..621965c8910 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/2.cc @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <version> + +#ifndef __cpp_lib_jthread +# error "Feature-test macro for jthread missing in <version>" +#elif __cpp_lib_jthread != 201907L +# error "Feature-test macro for jthread has wrong value in <version>" +#endif diff --git a/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc new file mode 100644 index 00000000000..c29db212167 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/jthread/jthread.cc @@ -0,0 +1,198 @@ +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// { dg-options "-std=gnu++2a" } +// { dg-do compile { target c++2a } } + +#include <thread> +#include <chrono> +#include <cassert> +#include <atomic> + +using namespace::std::literals; + +//------------------------------------------------------ + +void test_no_stop_token() +{ + // test the basic jthread API (not taking stop_token arg) + + assert(std::jthread::hardware_concurrency() == std::thread::hardware_concurrency()); + std::stop_token stoken; + assert(!stoken.stop_possible()); + { + std::jthread::id t1ID{std::this_thread::get_id()}; + std::atomic<bool> t1AllSet{false}; + std::jthread t1([&t1ID, &t1AllSet] { + t1ID = std::this_thread::get_id(); + t1AllSet.store(true); + for (int c='9'; c>='0'; --c) { + std::this_thread::sleep_for(222ms); + } + }); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + stoken = t1.get_stop_token(); + assert(!stoken.stop_requested()); + } + assert(stoken.stop_requested()); +} + +//------------------------------------------------------ + +void test_stop_token() +{ + // test the basic thread API (taking stop_token arg) + + std::stop_source ssource; + std::stop_source origsource; + assert(ssource.stop_possible()); + assert(!ssource.stop_requested()); + { + std::jthread::id t1ID{std::this_thread::get_id()}; + std::atomic<bool> t1AllSet{false}; + std::atomic<bool> t1done{false}; + std::jthread t1([&t1ID, &t1AllSet, &t1done] (std::stop_token st) { + // check some values of the started thread: + t1ID = std::this_thread::get_id(); + t1AllSet.store(true); + for (int i=0; !st.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + t1done.store(true); + }, + ssource.get_token()); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + // and check all values: + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + + std::this_thread::sleep_for(470ms); + origsource = std::move(ssource); + ssource = t1.get_stop_source(); + assert(!ssource.stop_requested()); + auto ret = ssource.request_stop(); + assert(ret); + ret = ssource.request_stop(); + assert(!ret); + assert(ssource.stop_requested()); + assert(!t1done.load()); + assert(!origsource.stop_requested()); + + std::this_thread::sleep_for(470ms); + origsource.request_stop(); + } + assert(origsource.stop_requested()); + assert(ssource.stop_requested()); +} + +//------------------------------------------------------ + +void test_join() +{ + std::stop_source ssource; + assert(ssource.stop_possible()); + { + std::jthread t1([](std::stop_token stoken) { + for (int i=0; !stoken.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + }); + ssource = t1.get_stop_source(); + std::jthread t2([ssource] () mutable { + for (int i=0; i < 10; ++i) { + std::this_thread::sleep_for(70ms); + } + ssource.request_stop(); + }); + // wait for all thread to finish: + t2.join(); + assert(!t2.joinable()); + assert(t1.joinable()); + t1.join(); + assert(!t1.joinable()); + } +} + +//------------------------------------------------------ + +void test_detach() +{ + std::stop_source ssource; + assert(ssource.stop_possible()); + std::atomic<bool> t1FinallyInterrupted{false}; + { + std::jthread t0; + std::jthread::id t1ID{std::this_thread::get_id()}; + bool t1IsInterrupted; + std::stop_token t1InterruptToken; + std::atomic<bool> t1AllSet{false}; + std::jthread t1([&t1ID, &t1IsInterrupted, &t1InterruptToken, &t1AllSet, &t1FinallyInterrupted] + (std::stop_token stoken) { + // check some values of the started thread: + t1ID = std::this_thread::get_id(); + t1InterruptToken = stoken; + t1IsInterrupted = stoken.stop_requested(); + assert(stoken.stop_possible()); + assert(!stoken.stop_requested()); + t1AllSet.store(true); + for (int i=0; !stoken.stop_requested(); ++i) { + std::this_thread::sleep_for(100ms); + } + t1FinallyInterrupted.store(true); + }); + for (int i=0; !t1AllSet.load(); ++i) { + std::this_thread::sleep_for(10ms); + } + assert(!t0.joinable()); + assert(t1.joinable()); + assert(t1ID == t1.get_id()); + assert(t1IsInterrupted == false); + assert(t1InterruptToken == t1.get_stop_source().get_token()); + ssource = t1.get_stop_source(); + assert(t1InterruptToken.stop_possible()); + assert(!t1InterruptToken.stop_requested()); + t1.detach(); + assert(!t1.joinable()); + } + + assert(!t1FinallyInterrupted.load()); + ssource.request_stop(); + assert(ssource.stop_requested()); + for (int i=0; !t1FinallyInterrupted.load() && i < 100; ++i) { + std::this_thread::sleep_for(100ms); + } + assert(t1FinallyInterrupted.load()); +} + +int main() +{ + std::set_terminate([](){ + assert(false); + }); + + test_no_stop_token(); + test_stop_token(); + test_join(); + test_detach(); +} + -- 2.21.0