gpt4 book ai didi

c++ - 在带有 std::ref 的 std::thread 中使用地址清理调用 std::invoke(std::forward(...)) 时的奇怪行为

转载 作者:行者123 更新时间:2023-12-01 14:25:16 28 4
gpt4 key购买 nike

问题

我正在尝试将 lambda 闭包传递给 std::thread,它使用任意封闭参数调用任意封闭函数。

template< class Function, class... Args > 
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
}
}

int main() {
int i = 3;
std::thread t = timed_thread(&print_int_ref, std::ref(i));
t.join()
return 0;
}

/*
[1]: https://stackoverflow.com/questions/26831382/capturing-perfectly-forwarded-variable-in-lambda
[2]: https://en.cppreference.com/w/cpp/thread/thread/thread
*/
  • 我使用 std::forward 以便右值引用和左值引用得到转发(正确调度)。
  • 因为 std::invoke 和 lambda 创建临时数据结构,调用者必须将引用包装在 std::ref 中。

该代码似乎可以工作,但会导致 stack-use-after-scope 并进行地址清理。这是我的主要困惑。

嫌疑人

我认为这可能与this error有关,但我没有看到关系,因为我没有返回引用;对i 的引用应该在main 的栈帧的持续时间内有效,这应该比线程持续时间更长,因为main 加入了它。引用通过拷贝 (std::reference_wrapper) 传递到 thread_thunk

我怀疑 args... 不能通过引用捕获,但是应该如何捕获呢?

次要混淆:更改 {std::thread t = timed_thread(blah); t.join();}(强制析构函数的大括号)到 timed_thread(blah).join(); 没有这样的问题,即使对我来说它们看起来是等价的。

最小示例

#include <functional>
#include <iostream>
#include <thread>

template <class T>
std::decay_t<T> _decay_copy(T&& v) { return std::forward<T>(v); }

template< class Function, class... Args >
std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {
std::cout << "Start thread timer" << std::endl;
// Regarding std::invoke(_decay_copy(...), ...), see (3) of [2].
// Assume no exception can be thrown from copying.
std::invoke(_decay_copy(std::forward<Function>(f)),
_decay_copy(std::forward<Args>(args)...));
std::cout << "End thread timer" << std::endl;
});

/* The single-threaded version code works perfectly */
// thread_thunk();
// return std::thread{[]{}};

/* multithreaded version appears to work
but triggers "stack-use-after-scope" with ASAN */
return std::thread{thread_thunk};
}

void print_int_ref(int& i) { std::cout << i << std::endl; }

int main() {
int i = 3;

/* This code appears to work
but triggers "stack-use-after-scope" with ASAN */
// {
// std::thread t = timed_thread(&print_int_ref, std::ref(i));
// t.join();
// }

/* This code works perfectly */
timed_thread(&print_int_ref, std::ref(i)).join();
return 0;
}

编译器命令:clang++ -pthread -std=c++17 -Wall -Wextra -fsanitize=address test.cpp && ./a.out。 Remvoe address 以查看其工作情况。

ASAN backtrace

最佳答案

这两个版本似乎都是未定义的行为。未定义的行为是否会被 sanitizer 捕获是家常便饭。如果程序重新运行足够多的次数,即使是所谓的工作版本也很可能也会触发 sanitizer 。错误在这里:

std::thread timed_thread(Function&& f, Args&&... args) {
// Regarding capturing perfectly-forwarded variables in lambda, see [1]
auto thread_thunk = ([&] {

闭包使用捕获的args 通过引用

如您所知,timed_thread 的参数超出范围并在 timed_thread 返回时被销毁。那是他们的范围。这就是 C++ 的工作原理。

但您不能保证,无论如何,这个闭包会被新的执行线程执行并引用捕获的,通过引用,所有的 args...,在它们烟消云散之前:

return std::thread{thread_thunk};

除非这个新线程设法执行 thread_hunk 中的代码,该代码引用捕获的通过引用 args... ,它会在这个函数返回后结束访问,这会导致未定义的行为。

关于c++ - 在带有 std::ref 的 std::thread 中使用地址清理调用 std::invoke(std::forward(...)) 时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62380904/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com