From b204d7722d30f44281dea3341070223475f1cff9 Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Thu, 19 Nov 2020 13:36:15 +0000 Subject: [PATCH] libstdc++: Move std::thread to a new header This makes it possible to use std::thread without including the whole of . It also makes this_thread::get_id() and this_thread::yield() available even when there is no gthreads support (e.g. when GCC is built with --disable-threads or --enable-threads=single). In order for the std::thread::id return type of this_thread::get_id() to be defined, std:thread itself is defined unconditionally. However the constructor that creates new threads is not defined for single-threaded builds. The thread::join() and thread::detach() member functions are defined inline for single-threaded builds and just throw an exception (because we know the thread cannot be joinable if the constructor that creates joinable threads doesn't exit). The thread::hardware_concurrency() member function is also defined inline and returns 0 (as suggested by the standard when the value "is not computable or well-defined"). The main benefit for most targets is that other headers such as do not need to include the whole of just to be able to create a std::thread. That avoids including and std::jthread where not required. This is another partial fix for PR 92546. This also means we can use this_thread::get_id() and this_thread::yield() in instead of using the gthread functions directly. This removes some preprocessor conditionals, simplifying the code. libstdc++-v3/ChangeLog: PR libstdc++/92546 * include/Makefile.am: Add new header. * include/Makefile.in: Regenerate. * include/std/future: Include new header instead of . * include/std/stop_token: Include new header instead of . (stop_token::_S_yield()): Use this_thread::yield(). (_Stop_state_t::_M_requester): Change type to std::thread::id. (_Stop_state_t::_M_request_stop()): Use this_thread::get_id(). (_Stop_state_t::_M_remove_callback(_Stop_cb*)): Likewise. Use __is_single_threaded() to decide whether to synchronize. * include/std/thread (thread, operator==, this_thread::get_id) (this_thread::yield): Move to new header. (operator<=>, operator!=, operator<, operator<=, operator>) (operator>=, hash, operator<<): Define even when gthreads not available. * src/c++11/thread.cc: Include . * include/bits/std_thread.h: New file. (thread, operator==, this_thread::get_id, this_thread::yield): Define even when gthreads not available. [!_GLIBCXX_HAS_GTHREADS] (thread::join, thread::detach) (thread::hardware_concurrency): Define inline. --- libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/bits/std_thread.h | 337 +++++++++++++++++++++++++ libstdc++-v3/include/std/future | 2 +- libstdc++-v3/include/std/stop_token | 37 +-- libstdc++-v3/include/std/thread | 266 +------------------ libstdc++-v3/src/c++11/thread.cc | 1 + 7 files changed, 362 insertions(+), 283 deletions(-) create mode 100644 libstdc++-v3/include/bits/std_thread.h diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 292d89da8ba..7979f1c589d 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -187,6 +187,7 @@ bits_headers = \ ${bits_srcdir}/std_abs.h \ ${bits_srcdir}/std_function.h \ ${bits_srcdir}/std_mutex.h \ + ${bits_srcdir}/std_thread.h \ ${bits_srcdir}/stl_algo.h \ ${bits_srcdir}/stl_algobase.h \ ${bits_srcdir}/stl_bvector.h \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index fff444c69e8..dfa72b95547 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -533,6 +533,7 @@ bits_headers = \ ${bits_srcdir}/std_abs.h \ ${bits_srcdir}/std_function.h \ ${bits_srcdir}/std_mutex.h \ + ${bits_srcdir}/std_thread.h \ ${bits_srcdir}/stl_algo.h \ ${bits_srcdir}/stl_algobase.h \ ${bits_srcdir}/stl_bvector.h \ diff --git a/libstdc++-v3/include/bits/std_thread.h b/libstdc++-v3/include/bits/std_thread.h new file mode 100644 index 00000000000..96c8d1bb19f --- /dev/null +++ b/libstdc++-v3/include/bits/std_thread.h @@ -0,0 +1,337 @@ +// std::thread declarations -*- C++ -*- + +// Copyright (C) 2008-2020 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. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +/** @file bits/std_thread.h + * This is an internal header file, included by other library headers. + * Do not attempt to use it directly. @headername{thread} + */ + +#ifndef _GLIBCXX_THREAD_H +#define _GLIBCXX_THREAD_H 1 + +#pragma GCC system_header + +#if __cplusplus >= 201103L +#include + +#include // std::terminate +#include // std::basic_ostream +#include // std::tuple +#include // std::__invoke +#include // not required, but helpful to users +#include // std::unique_ptr + +#ifdef _GLIBCXX_HAS_GTHREADS +# include +#else +# include +# include +#endif + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + + /** @addtogroup threads + * @{ + */ + + /// thread + class thread + { + public: +#ifdef _GLIBCXX_HAS_GTHREADS + // Abstract base class for types that wrap arbitrary functors to be + // invoked in the new thread of execution. + struct _State + { + virtual ~_State(); + virtual void _M_run() = 0; + }; + using _State_ptr = unique_ptr<_State>; + + using native_handle_type = __gthread_t; +#else + using native_handle_type = int; +#endif + + /// thread::id + class id + { + native_handle_type _M_thread; + + public: + id() noexcept : _M_thread() { } + + explicit + id(native_handle_type __id) : _M_thread(__id) { } + + private: + friend class thread; + friend struct hash; + + friend bool + operator==(id __x, id __y) noexcept; + +#if __cpp_lib_three_way_comparison + friend strong_ordering + operator<=>(id __x, id __y) noexcept; +#else + friend bool + operator<(id __x, id __y) noexcept; +#endif + + template + friend basic_ostream<_CharT, _Traits>& + operator<<(basic_ostream<_CharT, _Traits>& __out, id __id); + }; + + private: + id _M_id; + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 2097. packaged_task constructors should be constrained + // 3039. Unnecessary decay in thread and packaged_task + template + using __not_same = __not_, thread>>; + + public: + thread() noexcept = default; + +#ifdef _GLIBCXX_HAS_GTHREADS + template>> + explicit + thread(_Callable&& __f, _Args&&... __args) + { + static_assert( __is_invocable::type, + typename decay<_Args>::type...>::value, + "std::thread arguments must be invocable after conversion to rvalues" + ); + +#ifdef GTHR_ACTIVE_PROXY + // Create a reference to pthread_create, not just the gthr weak symbol. + auto __depend = reinterpret_cast(&pthread_create); +#else + auto __depend = nullptr; +#endif + using _Wrapper = _Call_wrapper<_Callable, _Args...>; + // Create a call wrapper with DECAY_COPY(__f) as its target object + // and DECAY_COPY(__args)... as its bound argument entities. + _M_start_thread(_State_ptr(new _State_impl<_Wrapper>( + std::forward<_Callable>(__f), std::forward<_Args>(__args)...)), + __depend); + } +#endif // _GLIBCXX_HAS_GTHREADS + + ~thread() + { + if (joinable()) + std::terminate(); + } + + thread(const thread&) = delete; + + thread(thread&& __t) noexcept + { swap(__t); } + + thread& operator=(const thread&) = delete; + + thread& operator=(thread&& __t) noexcept + { + if (joinable()) + std::terminate(); + swap(__t); + return *this; + } + + void + swap(thread& __t) noexcept + { std::swap(_M_id, __t._M_id); } + + bool + joinable() const noexcept + { return !(_M_id == id()); } + + void + join(); + + void + detach(); + + id + get_id() const noexcept + { return _M_id; } + + /** @pre thread is joinable + */ + native_handle_type + native_handle() + { return _M_id._M_thread; } + + // Returns a value that hints at the number of hardware thread contexts. + static unsigned int + hardware_concurrency() noexcept; + +#ifdef _GLIBCXX_HAS_GTHREADS + private: + template + struct _State_impl : public _State + { + _Callable _M_func; + + template + _State_impl(_Args&&... __args) + : _M_func{{std::forward<_Args>(__args)...}} + { } + + void + _M_run() { _M_func(); } + }; + + void + _M_start_thread(_State_ptr, void (*)()); + +#if _GLIBCXX_THREAD_ABI_COMPAT + public: + struct _Impl_base; + typedef shared_ptr<_Impl_base> __shared_base_type; + struct _Impl_base + { + __shared_base_type _M_this_ptr; + virtual ~_Impl_base() = default; + virtual void _M_run() = 0; + }; + + private: + void + _M_start_thread(__shared_base_type, void (*)()); + + void + _M_start_thread(__shared_base_type); +#endif + + private: + // A call wrapper that does INVOKE(forwarded tuple elements...) + template + struct _Invoker + { + _Tuple _M_t; + + template + struct __result; + template + struct __result> + : __invoke_result<_Fn, _Args...> + { }; + + template + typename __result<_Tuple>::type + _M_invoke(_Index_tuple<_Ind...>) + { return std::__invoke(std::get<_Ind>(std::move(_M_t))...); } + + typename __result<_Tuple>::type + operator()() + { + using _Indices + = typename _Build_index_tuple::value>::__type; + return _M_invoke(_Indices()); + } + }; + + public: + template + using _Call_wrapper = _Invoker::type...>>; +#endif // _GLIBCXX_HAS_GTHREADS + }; + +#ifndef _GLIBCXX_HAS_GTHREADS + inline void thread::join() { std::__throw_system_error(EINVAL); } + inline void thread::detach() { std::__throw_system_error(EINVAL); } + inline unsigned int thread::hardware_concurrency() { return 0; } +#endif + + inline void + swap(thread& __x, thread& __y) noexcept + { __x.swap(__y); } + + inline bool + operator==(thread::id __x, thread::id __y) noexcept + { + // pthread_equal is undefined if either thread ID is not valid, so we + // can't safely use __gthread_equal on default-constructed values (nor + // the non-zero value returned by this_thread::get_id() for + // single-threaded programs using GNU libc). Assume EqualityComparable. + return __x._M_thread == __y._M_thread; + } + + // N.B. other comparison operators are defined in + + namespace this_thread + { + /// this_thread::get_id + inline thread::id + get_id() noexcept + { +#ifdef _GLIBCXX_HAS_GTHREADS + +#ifdef __GLIBC__ + // For the GNU C library pthread_self() is usable without linking to + // libpthread, but prior to version 2.27 the version in libc returns 0, + // which breaks the invariant this_thread::get_id() != thread::id{}. + // + // We know that pthread_t is a scalar type in the GNU C library, + // so just use (__gthread_t)1 as the ID of the main (and only) thread. + // + // This uses __gthread_active_p not __gnu_cxx::__is_single_threaded + // because we don't want the thread::id of the main thread to change + // if additional threads are created later. + if (!__gthread_active_p()) + return thread::id((__gthread_t)1); +#endif + + return thread::id(__gthread_self()); +#else + return thread::id(1); +#endif + } + + /// this_thread::yield + inline void + yield() noexcept + { +#if defined _GLIBCXX_HAS_GTHREADS && defined _GLIBCXX_USE_SCHED_YIELD + __gthread_yield(); +#endif + } + + } // namespace this_thread + + /// @} + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace +#endif // C++11 + +#endif // _GLIBCXX_THREAD_H diff --git a/libstdc++-v3/include/std/future b/libstdc++-v3/include/std/future index f7617cac8e9..77b6f930f00 100644 --- a/libstdc++-v3/include/std/future +++ b/libstdc++-v3/include/std/future @@ -36,7 +36,6 @@ #else #include // call_once -#include #include // __at_thread_exit_elt #include #include @@ -46,6 +45,7 @@ #include #include #include +#include #include #include diff --git a/libstdc++-v3/include/std/stop_token b/libstdc++-v3/include/std/stop_token index 7cd01c9713e..5b2d5a0427c 100644 --- a/libstdc++-v3/include/std/stop_token +++ b/libstdc++-v3/include/std/stop_token @@ -32,15 +32,14 @@ #if __cplusplus > 201703L #include +#include -#ifdef _GLIBCXX_HAS_GTHREADS -# define __cpp_lib_jthread 201911L -# include -# if __has_include() -# include -# endif +#if __has_include() +# include #endif +#define __cpp_lib_jthread 201911L + namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION @@ -105,9 +104,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { #if defined __i386__ || defined __x86_64__ __builtin_ia32_pause(); -#elif defined _GLIBCXX_HAS_GTHREADS && defined _GLIBCXX_USE_SCHED_YIELD - __gthread_yield(); #endif + this_thread::yield(); } #ifndef __cpp_lib_semaphore @@ -162,18 +160,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION std::atomic _M_owners{1}; std::atomic _M_value{_S_ssrc_counter_inc}; _Stop_cb* _M_head = nullptr; - struct - { -#ifdef _GLIBCXX_HAS_GTHREADS - __gthread_t _M_id; - void _M_set() { _M_id = __gthread_self(); } - bool _M_is_current_thread() const - { return __gthread_equal(_M_id, __gthread_self()); } -#else - void _M_set() { } - constexpr bool _M_is_current_thread() const { return true; } -#endif - } _M_requester; + std::thread::id _M_requester; _Stop_state_t() = default; @@ -246,7 +233,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } while (!_M_try_lock_and_stop(__old)); - _M_requester._M_set(); + _M_requester = this_thread::get_id(); while (_M_head) { @@ -273,10 +260,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION if (!__destroyed) { __cb->_M_destroyed = nullptr; -#ifdef _GLIBCXX_HAS_GTHREADS + // synchronize with destructor of stop_callback that owns *__cb - __cb->_M_done.release(); -#endif + if (!__gnu_cxx::__is_single_threaded()) + __cb->_M_done.release(); } // Avoid relocking if we already know there are no more callbacks. @@ -353,7 +340,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Despite appearances there is no data race on _M_requester. The only // write to it happens before the callback is removed from the list, // and removing it from the list happens before this read. - if (!_M_requester._M_is_current_thread()) + if (!(_M_requester == this_thread::get_id())) { // Synchronize with completion of callback. __cb->_M_done.acquire(); diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread index 080036e2609..6ea8a51c0cf 100644 --- a/libstdc++-v3/include/std/thread +++ b/libstdc++-v3/include/std/thread @@ -37,26 +37,18 @@ #include // std::chrono::* -#ifdef _GLIBCXX_USE_NANOSLEEP -# include // errno, EINTR -# include // nanosleep -#endif - -#if defined(_GLIBCXX_HAS_GTHREADS) -#include - -#include // std::unique_ptr -#include // std::tuple - #if __cplusplus > 201703L # include // std::strong_ordering # include // std::stop_source, std::stop_token, std::nostopstate #endif +#include // std::thread, get_id, yield #include // std::hash -#include // std::__invoke -#endif // _GLIBCXX_HAS_GTHREADS +#ifdef _GLIBCXX_USE_NANOSLEEP +# include // errno, EINTR +# include // nanosleep +#endif namespace std _GLIBCXX_VISIBILITY(default) { @@ -70,221 +62,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * @{ */ -#if defined(_GLIBCXX_HAS_GTHREADS) - /// thread - class thread - { - public: - // Abstract base class for types that wrap arbitrary functors to be - // invoked in the new thread of execution. - struct _State - { - virtual ~_State(); - virtual void _M_run() = 0; - }; - using _State_ptr = unique_ptr<_State>; - - typedef __gthread_t native_handle_type; - - /// thread::id - class id - { - native_handle_type _M_thread; - - public: - id() noexcept : _M_thread() { } - - explicit - id(native_handle_type __id) : _M_thread(__id) { } - - private: - friend class thread; - friend struct hash; - - friend bool - operator==(id __x, id __y) noexcept; - -#if __cpp_lib_three_way_comparison - friend strong_ordering - operator<=>(id __x, id __y) noexcept; -#else - friend bool - operator<(id __x, id __y) noexcept; -#endif - - template - friend basic_ostream<_CharT, _Traits>& - operator<<(basic_ostream<_CharT, _Traits>& __out, id __id); - }; - - private: - id _M_id; - - // _GLIBCXX_RESOLVE_LIB_DEFECTS - // 2097. packaged_task constructors should be constrained - // 3039. Unnecessary decay in thread and packaged_task - template - using __not_same = __not_, thread>>; - - public: - thread() noexcept = default; - - template>> - explicit - thread(_Callable&& __f, _Args&&... __args) - { - static_assert( __is_invocable::type, - typename decay<_Args>::type...>::value, - "std::thread arguments must be invocable after conversion to rvalues" - ); - -#ifdef GTHR_ACTIVE_PROXY - // Create a reference to pthread_create, not just the gthr weak symbol. - auto __depend = reinterpret_cast(&pthread_create); -#else - auto __depend = nullptr; -#endif - using _Wrapper = _Call_wrapper<_Callable, _Args...>; - // Create a call wrapper with DECAY_COPY(__f) as its target object - // and DECAY_COPY(__args)... as its bound argument entities. - _M_start_thread(_State_ptr(new _State_impl<_Wrapper>( - std::forward<_Callable>(__f), std::forward<_Args>(__args)...)), - __depend); - } - - ~thread() - { - if (joinable()) - std::terminate(); - } - - thread(const thread&) = delete; - - thread(thread&& __t) noexcept - { swap(__t); } - - thread& operator=(const thread&) = delete; - - thread& operator=(thread&& __t) noexcept - { - if (joinable()) - std::terminate(); - swap(__t); - return *this; - } - - void - swap(thread& __t) noexcept - { std::swap(_M_id, __t._M_id); } - - bool - joinable() const noexcept - { return !(_M_id == id()); } - - void - join(); - - void - detach(); - - id - get_id() const noexcept - { return _M_id; } - - /** @pre thread is joinable - */ - native_handle_type - native_handle() - { return _M_id._M_thread; } - - // Returns a value that hints at the number of hardware thread contexts. - static unsigned int - hardware_concurrency() noexcept; - - private: - template - struct _State_impl : public _State - { - _Callable _M_func; - - template - _State_impl(_Args&&... __args) - : _M_func{{std::forward<_Args>(__args)...}} - { } - - void - _M_run() { _M_func(); } - }; - - void - _M_start_thread(_State_ptr, void (*)()); - -#if _GLIBCXX_THREAD_ABI_COMPAT - public: - struct _Impl_base; - typedef shared_ptr<_Impl_base> __shared_base_type; - struct _Impl_base - { - __shared_base_type _M_this_ptr; - virtual ~_Impl_base() = default; - virtual void _M_run() = 0; - }; - - private: - void - _M_start_thread(__shared_base_type, void (*)()); - - void - _M_start_thread(__shared_base_type); -#endif - - private: - // A call wrapper that does INVOKE(forwarded tuple elements...) - template - struct _Invoker - { - _Tuple _M_t; - - template - struct __result; - template - struct __result> - : __invoke_result<_Fn, _Args...> - { }; - - template - typename __result<_Tuple>::type - _M_invoke(_Index_tuple<_Ind...>) - { return std::__invoke(std::get<_Ind>(std::move(_M_t))...); } - - typename __result<_Tuple>::type - operator()() - { - using _Indices - = typename _Build_index_tuple::value>::__type; - return _M_invoke(_Indices()); - } - }; - - public: - template - using _Call_wrapper = _Invoker::type...>>; - }; - - inline void - swap(thread& __x, thread& __y) noexcept - { __x.swap(__y); } - - inline bool - operator==(thread::id __x, thread::id __y) noexcept - { - // pthread_equal is undefined if either thread ID is not valid, so we - // can't safely use __gthread_equal on default-constructed values (nor - // the non-zero value returned by this_thread::get_id() for - // single-threaded programs using GNU libc). Assume EqualityComparable. - return __x._M_thread == __y._M_thread; - } + // std::thread is defined in #if __cpp_lib_three_way_comparison inline strong_ordering @@ -336,7 +114,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION else return __out << __id._M_thread; } -#endif // _GLIBCXX_HAS_GTHREADS /** @namespace std::this_thread * @brief ISO C++ 2011 namespace for interacting with the current thread @@ -345,36 +122,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ namespace this_thread { -#if defined _GLIBCXX_HAS_GTHREADS - /// get_id - inline thread::id - get_id() noexcept - { -#ifdef __GLIBC__ - // For the GNU C library pthread_self() is usable without linking to - // libpthread.so but returns 0, so we cannot use it in single-threaded - // programs, because this_thread::get_id() != thread::id{} must be true. - // We know that pthread_t is an integral type in the GNU C library. - if (!__gthread_active_p()) - return thread::id(1); -#endif - return thread::id(__gthread_self()); - } -#endif // _GLIBCXX_HAS_GTHREADS - - /// yield - inline void - yield() noexcept - { -#if defined _GLIBCXX_HAS_GTHREADS && defined _GLIBCXX_USE_SCHED_YIELD - __gthread_yield(); -#endif - } - void __sleep_for(chrono::seconds, chrono::nanoseconds); - /// sleep_for + /// this_thread::sleep_for template inline void sleep_for(const chrono::duration<_Rep, _Period>& __rtime) @@ -396,7 +147,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif } - /// sleep_until + /// this_thread::sleep_until template inline void sleep_until(const chrono::time_point<_Clock, _Duration>& __atime) @@ -421,6 +172,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #ifdef __cpp_lib_jthread + /// A thread that can be requested to stop and automatically joined. class jthread { public: diff --git a/libstdc++-v3/src/c++11/thread.cc b/libstdc++-v3/src/c++11/thread.cc index a4c87d816a5..e4dd1687a4b 100644 --- a/libstdc++-v3/src/c++11/thread.cc +++ b/libstdc++-v3/src/c++11/thread.cc @@ -24,6 +24,7 @@ #define _GLIBCXX_THREAD_ABI_COMPAT 1 +#include // include this first so can use shared_ptr #include #include #include -- 2.30.2