通过std :: thread传递rvalues

时间:2018-03-20 15:06:31

标签: c++ multithreading visual-c++ stl c++17

这就是为什么这个代码工作的问题。我想知道如何修复命名空间dj中的代码,而不是在演示程序中。您可能希望在进一步阅读之前运行该程序。

当我通过rvalue std::string通过std::thread时,字符串到达​​并行函数为空。也许原始值已经被std :: move,但最终出现在了错误的地方。但我认为问题可能在于函数timer。在那里我认为字符串是通过引用捕获的,当rvalue消失时,引用无效。

// In the demo program, the string is the one element of `...args`.
template<typename F, typename... Args>
void timer(double seconds, F& f, Args&&... args) {
    auto th = std::thread(  
        [&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
    th.detach();
}

我无法弄清楚如何捕捉lambda的东西。 &符吓到了我,但我无法解决它。我已经尝试过使用bindfunction而不是lambda。没有快乐。

...

演示程序......主线程开始暂停一段给定的秒数。然后新线程打印一个字符串并发出蜂鸣声,直到主线程将原子bool设置为true。要显示它不起作用,请将全局bool demo_the_problem设置为true。字符串到达​​警报功能为空。

#include <thread>
#include <chrono>
#include <iostream>

static bool demo_the_problem = false;

namespace dj {

    inline void std_sleep(long double seconds) noexcept
    {    
        using duration_t = std::chrono::duration<long long, std::nano>;
        const auto duration =  duration_t(static_cast<long long> (seconds * 1e9));
        std::this_thread::sleep_for(duration);
    }

    // Runs a command f after delaying an amount of time
    template<typename F, typename... Args>
    auto delayed(double seconds, F& f, Args&&... args) {
        std_sleep(seconds);
        return f(std::forward<Args>(args)...);
    }

    // Runs a function after a given delay. Returns nothing.
    template<typename F, typename... Args>
    void timer(double seconds, F& f, Args&&... args) {
        auto th = std::thread( // XXX This appears to be where I lose ring_tone. XXX
            [&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
        th.detach();
    }

}

using namespace dj;

int main() {

    std::atomic<bool> off_button(false);

    // Test dj::timer, which invokes a void function after a given
    // period of time. In this case, the function is "alarm_clock".
    auto alarm_clock = [&off_button](const std::string ring_tone) {
        char bel = 7;
        std::cout << ring_tone << '\n';
        while (!off_button) {
            std_sleep(0.5);
            std::cout << bel;
        }
        off_button = false;
    };

    auto ring = std::string("BRINNNNGGG!");
    if (demo_the_problem)
        timer(4.0, alarm_clock, std::string("BRINNNNGGG!")); // Not OK - ring arrives as empty string
    else {
        timer(4.0, alarm_clock, ring);  // Ring tone arrives intact.
    }


    // Mess around for a while
    for (int i = 0; i < 12; ++i) {
        if (i == 7) {
            off_button = true; // Hit the button to turn off the alarm
        }
        std::cout << "TICK ";
        std_sleep(0.5);
        std::cout << "tock ";
        std_sleep(0.5);
    }

    // and wait for the button to pop back up.
    while(off_button) std::this_thread::yield();
    std::cout << "Yawn." << std::endl;
    return 0;
}

1 个答案:

答案 0 :(得分:4)

template<typename F, typename... Args>
void timer(double seconds, F& f, Args&&... args) {
  auto th = std::thread(  
    [&, seconds] { delayed(seconds, f, std::forward<Args>(args)...); });
  th.detach();
}

这导致未定义的行为,因为引用捕获的数据销毁相对于线程中的代码没有排序。

永远不要在lambda上使用&,其生命周期(或其副本)比当前范围更长。周期。

你的detach也是代码味道;没有切实可行的方法来确定线程是否在main结束之前完成,并且主线的行数超过主要具有未指定的行为。这是C ++,您负责清理资源使用情况。找到解决方案。我现在暂时忽略它。

template<typename F, typename... Args>
void timer(double seconds, F&& f, Args&&... args) {
  auto th = std::thread(  
    [seconds,
     f=std::forward<F>(f),
     tup=std::make_tuple(std::forward<Args>(args)...)
    ]
    {
// TODO:      delayed(seconds, f, std::forward<Args>(args)...);
    }
  );
  th.detach();
}

现在我们只需要编写// TODO行。

中,这很容易。

template<typename F, typename... Args>
void timer(double seconds, F&& f, Args&&... args) {
  auto th = std::thread(  
    [seconds,
     f=std::forward<F>(f),
     tup=std::make_tuple(std::forward<Args>(args)...)
    ]() mutable
    {
      std::apply(
        [&](auto&&...args){
          delayed(seconds, f, decltype(args)(args)...);
        },
        std::move(tup)
      );
    }
  );
  th.detach();
}

请注意,此结果所有内容复制到线程中。如果您确实想要传递左值引用,请使用std::ref,这主要使它适用于您。

中,您最好的解决方案是编写自己的notstd::apply。有很多人写过这篇文章,包括myself here

注意我在lambda中使用了[&]; lambda并不比当前范围更长(实际上它并不比当前的线更长)。这是您应该使用的唯一情况[&]