简体   繁体   中英

Passing rvalues through std::thread

This is a why-isn't-this-code-working question. I want to know how to fix the code in namespace dj, not in the demo program. You might want to run the program before reading further.

When I pass an rvalue std::string via std::thread , the string arrives in the parallel function empty. Perhaps the original value has been std::moved, but ended up in the wrong place. But I think the problem is probably in function timer . There I think the string is captured by reference and when the rvalue disappears, the reference is invalid.

// 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();
}

I can't figure out how to capture things for the lambda. The ampersand scares me, but I have not been able to work around it. I have made various attempts at using bind or function instead of a lambda. No joy.

...

Demo program... The main thread starts a tread that pauses for a given number of seconds. The new thread then prints a string and beeps until the main thread sets an atomic bool to true. To show it not working, set the global bool demo_the_problem to true. The string arrives in the alarm function empty.

#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;
}
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();
}

this results in undefined behavior as the reference captured data destruction is not sequenced relative to the code in the thread.

Never, ever use & on a lambda whose lifetime (or copies of it) outlive the current scope. Period.

Your detach is also code smell; there is no practical way to determine if the thread finishes before the end of main , and threads that outlive main have unspecified behavior. This is C++, you are responsible for cleaning up your resource usage. Find a solution for that. I'll ignore it for now.

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();
}

Now we just need to write the // TODO line.

In ` this is easy.

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();
}

Note that this result copies everything into the thread. If you really, really want to pass an lvalue reference in, use std::ref which mostly makes it work for you.

In or your best solution is to write your own notstd::apply . There are many people who have written this, including myself here .

Notice I used [&] in a lambda; that lambda does not outlive the current scope (in fact it doesn't outlive the current line). That is the only situation you should ever use [&] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM