【问题标题】:Pass parameters to std::thread wrapper将参数传递给 std::thread 包装器
【发布时间】:2017-06-05 02:31:32
【问题描述】:

我想实现一个小型线程包装器,它提供线程是否仍处于活动状态或线程是否已完成其工作的信息。为此,我需要将要由线程类执行的函数及其参数传递给另一个函数。我有一个简单的实现应该可以工作,但无法编译,我不知道该怎么做才能让它工作。

这是我的代码:

#include <unistd.h>
#include <iomanip>
#include <iostream>
#include <thread>
#include <utility>

class ManagedThread
{
public:
   template< class Function, class... Args> explicit ManagedThread( Function&& f, Args&&... args);
   bool isActive() const { return mActive; }
private:
   volatile bool  mActive;
   std::thread    mThread;
};

template< class Function, class... Args>
   void threadFunction( volatile bool& active_flag, Function&& f, Args&&... args)
{
   active_flag = true;
   f( args...);
   active_flag = false;
}

template< class Function, class... Args>
   ManagedThread::ManagedThread( Function&& f, Args&&... args):
      mActive( false),
      mThread( threadFunction< Function, Args...>, std::ref( mActive), f, args...)
{
}

static void func() { std::cout << "thread 1" << std::endl; }

int main() {
   ManagedThread  mt1( func);
   std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive() << std::endl;
   ::sleep( 1);
   std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive() << std::endl;

   return 0;
}

我得到的编译器错误:

In file included from /usr/include/c++/5/thread:39:0,
                 from prog.cpp:4:
/usr/include/c++/5/functional: In instantiation of 'struct std::_Bind_simple<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>':
/usr/include/c++/5/thread:137:59:   required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(volatile bool&, void (&)()); _Args = {std::reference_wrapper<volatile bool>, void (&)()}]'
prog.cpp:28:82:   required from 'ManagedThread::ManagedThread(Function&&, Args&& ...) [with Function = void (&)(); Args = {}]'
prog.cpp:35:28:   required from here
/usr/include/c++/5/functional:1505:61: error: no type named 'type' in 'class std::result_of<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>'
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/5/functional:1526:9: error: no type named 'type' in 'class std::result_of<void (*(std::reference_wrapper<volatile bool>, void (*)()))(volatile bool&, void (&)())>'
         _M_invoke(_Index_tuple<_Indices...>)
         ^

现场示例可在此处获得:https://ideone.com/jhBF1q

【问题讨论】:

  • 我在转发参数方面没有太多经验,但我只是觉得你错过了一个或两个 std::forward 电话......你可能想阅读"When to use std::forward to forward arguments?"。跨度>
  • 另请注意,您可能无法获得预期的结果。您创建的线程可能会在您第一次调用mt1.isActive() 之前启动、运行和完成。考虑在线程函数中添加一个短暂的睡眠,然后在main 函数中添加一个更长的睡眠。
  • 我也这么认为,但我添加的每个 std::forward 调用实际上都让情况变得更糟,即导致更多的编译器错误。也许如果当前问题得到解决,我将需要(并且能够)在我的函数中应用 std::forward 调用,但首先我需要让线程构造函数调用工作。
  • 关于时机:你是对的,但这不是一个真正的问题。重要的是在线程完成后获取isActive() == false 信息。
  • 最后,关于您的设计的另一个小想法:为什么不让threadFunction 成为包装器的member 函数?那么你不需要将mActive 标志作为参数传递给函数。

标签: c++ c++14


【解决方案1】:

在错误消息中,您可以看到void (*)()void (&amp;)() 的区别。那是因为std::thread's constructor 参数是std::decayed

std::ref 也添加到f

template< class Function, class... Args>
   ManagedThread::ManagedThread( Function&& f, Args&&... args):
      mActive( false),
      mThread( threadFunction< Function, Args...>, std::ref(mActive), std::ref(f), std::forward<Args>(args)...)
{
}

【讨论】:

  • 谢谢,编译成功!伟大的!目前我得到“没有活动异常的终止调用”,但这是我必须研究的另一个问题。我读到了关于衰减参数的内容,但我没有意识到这会以何种方式影响我的功能。我将不得不看看std::forward&lt; Args&gt;( args)... 是如何工作的......
  • 请参阅@jotik's answer 了解终止呼叫的解释和更正。
【解决方案2】:

@O'Neil 的答案是正确的,但我想提供一个简单的 lambda 方法,因为您已将其标记为 C++14

template<class Function, class... Args>
ManagedThread::ManagedThread(Function&& f, Args&&... args):
      mActive(false),
      mThread([&] /*()*/ { // uncomment if C++11 compatibility needed
        mActive = true;
        std::forward<Function>(f)(std::forward<Args>(args)...);
        mActive = false;
      })
{}

这将消除对外部函数的需求。

【讨论】:

  • 谢谢,我也试试这个版本!
  • 这有一个微妙的错误,因为构造函数可以在线程开始执行之前返回并转发fargs,从而导致use-after-free堆栈错误。
【解决方案3】:

O'Neil 和 DeiDei 先到了这里,据我所知他们是正确的。但是,我仍在发布我对您的问题的解决方案。

这里有一些更好的方法:

#include <atomic>
#include <thread>
#include <utility>

class ManagedThread {

public: /* Methods: */

    template <class F, class ... Args>
    explicit ManagedThread(F && f, Args && ... args)
        : m_thread(
            [func=std::forward<F>(f), flag=&m_active](Args && ... args)
                    noexcept(noexcept(f(std::forward<Args>(args)...)))
            {
                func(std::forward<Args>(args)...);
                flag->store(false, std::memory_order_release);
            },
            std::forward<Args>(args)...)
    {}

    bool isActive() const noexcept
    { return m_active.load(std::memory_order_acquire); }

private: /* Fields: */

    std::atomic<bool> m_active{true};
    std::thread m_thread;

};

它改用了 lambda,并正确地使用 std::atomic&lt;bool&gt; 而不是 volatile 来同步状态,并且还包括适当的 noexcept() 说明符。

另请注意,底层std::thread 在销毁之前未正确连接或分离,因此导致std::terminate() 被调用。

我也重写了测试代码:

#include <chrono>
#include <iostream>

int main() {
    ManagedThread mt1(
        []() noexcept
        { std::this_thread::sleep_for(std::chrono::milliseconds(500)); });
    std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
              << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
              << std::endl;
}

【讨论】:

  • 也感谢您的解决方案。我没有故意使用原子,因为只有 1 个线程更改标志的值,而另一个只是读取它。
  • 关于终止:哦,对了,当然,我忘记了调用join()的析构函数。
  • @Rene 你仍然需要原子。 Afaik volatile 在一般情况下是不够的。见this answer
【解决方案4】:

以下内容简单、优雅,据我所知是当前所有答案中最正确的(见下文):

template < class Function, class... Args >
ManagedThread::ManagedThread( Function&& fn, Args&&... args ) :
    mActive(false),
    mThread(
        [this]( auto&& fn2, auto&&... args2 ) -> void {
            mActive = true;
            fn2(std::forward<Args>(args2)...);
            mActive = false;
        },
        std::forward<Function>(fn), std::forward<Args>(args)...
    )
{}

DeiDei 的The answer 有一个微妙但重要的缺陷:lambda 通过引用获取堆栈变量,因此如果线程启动并尝试使用构造函数返回后的堆栈变量,您可以获得堆栈使用-释放后错误。实际上,使用-fsanitize=address 编译通常足以证明问题。

The answer by O'Neill 在例如任何参数都是左值,而且有点笨拙。

The answer by jotik 很接近,但当参数是左值或成员函数时也不起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-13
    • 2020-12-15
    • 2016-04-16
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 2013-04-24
    相关资源
    最近更新 更多