public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] Add C++20 jthread type to <thread> (2nd attempt)
@ 2019-10-23 19:45 Thomas Rodgers
  0 siblings, 0 replies; only message in thread
From: Thomas Rodgers @ 2019-10-23 19:45 UTC (permalink / raw)
  To: gcc-patches, libstdc++

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Le Patch --]
[-- Type: text/x-patch, Size: 15429 bytes --]

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  <trodgers@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  <trodgers@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

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2019-10-23 19:45 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-23 19:45 [PATCH] Add C++20 jthread type to <thread> (2nd attempt) Thomas Rodgers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).