调用 std::future::get() 后线程真的启动了吗?
Does a thread really start after std::future::get() is called?
我创建了一个 for 循环来定义 std::future
的 std::vector
来执行我的函数 vector<int> identify
和另一个循环来通过调用 std::future::get()
来获取结果,如下所示:
for (int i = 0; i < NUM_THREADS; ++i) {
VecString::const_iterator first = dirList.begin() + i*share;
VecString::const_iterator last = i == NUM_THREADS - 1 ? dirList.end() : dirList.begin() + (i + 1)*share;
VecString job(first, last);
futures[i] = async( launch::async, [=]() -> VecInt {
return identify(i, job, make_tuple( bIDList, wIDList, pIDList, descriptor), testingDir, binaryMode, logFile, logFile2 );
} );
}
int correct = 0;
int numImages = 0;
for( int i = 0; i != NUM_THREADS; ++i ) {
VecInt ret = futures[i].get();
correct += ret[0];
numImages += ret[1];
}
工作是处理一些图像,我将工作大致平均分配给每个线程。我还在函数中嵌入 std::cout
以指示结果来自哪个线程。
我希望在第一个线程完成工作后,其他线程也应该完成他们的工作并且循环将打印出结果。但是,在第一个线程完成后,其他线程仍在工作。我认为它们确实有效,而不仅仅是打印结果,因为函数处理大图像时会有一些延迟。这让我想知道线程何时真正开始。
我从文档中知道每个线程在初始化后立即启动,但您如何解释我的观察结果?非常感谢,非常感谢您的帮助
通常,在未来模式中 get
通常会阻塞,直到结果设置为未来。这个结果也可以是一个例外。因此从另一个线程设置一个结果将解除阻塞获取。如果异常传播到未来,也会发生同样的情况。
这是 link 到 cppreference.com 描述的:
The get method waits until the future has a valid result and (depending on which template is used) retrieves it. It effectively calls wait() in order to wait for the result.
线程不能保证 运行 按照您创建它们的顺序,甚至按照应用程序分配的顺序。为它做。
可能会发生,你很幸运,等待未来,它会为最后一个排队的作业提供结果,因此所有其他作业都已完成,你不会注意到任何阻塞,但反之亦然.
std::async:不保证任何异步执行。这是 reference states:
The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future that will eventually hold the result of that function call.
进一步说明:
If the async flag is set (i.e. policy & std::launch::async != 0), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned by std::thread(std::forward(f), std::forward(args)...), except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future that async returns to the caller.
您能否先尝试 运行 没有任何策略的 std::async
版本,它 should/might 重用内部线程池。如果它 运行s 更快,那么问题可能是应用程序没有重新使用线程?
最后,对 async 的引用有一个注释,说明何时 执行可以是同步的:
The implementation may extend the behavior of the first overload of std::async by enabling additional (implementation-defined) bits in the default launch policy.
Examples of implementation-defined launch policies are the sync policy (execute immediately, within the async call) and the task policy (similar to async, but thread-locals are not cleared)
If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes, essentially making code such as the following synchronous:
std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes
调试多线程应用程序有点困难。我建议只创建一个额外的线程并使所有 job/futures 运行 通过它,看看在执行过程中是否存在一些误解。此时主线程不会打扰,因为它只是等待结果。
您还可以使用一些线程安全的日志库(例如 Boost Log)并记录那里发生的事情以及 std::async
通过注销线程 ID 创建了多少个不同的线程,如果这些线程完全被重新使用。
因为您正在使用 std::launch::async
,所以由 std::async
决定如何安排您的请求。根据cppreference.com:
The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future
that will eventually hold the result of that function call.
它确实保证它们将被线程化,但是,您可以推断,您的 lambda 的评估将安排在下一个可用的机会发生:
If the async flag is set (i.e. policy & std::launch::async != 0
), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned by std::thread(std::forward<F>(f), std::forward<Args>(args)...)
, except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future
that async returns to the caller.
但是,就您的问题而言,您只是想知道与您对 get
的调用相关的执行时间。很容易证明 get
与 std::launch::async
:
启动时异步任务的执行无关
#include <iostream>
#include <future>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
int main() {
auto start = chrono::steady_clock::now();
auto timestamp = [start]( ostream & s )->ostream& {
auto now = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<chrono::microseconds>(now - start);
return s << "[" << elapsed.count() << "us] ";
};
vector<future<int>> futures;
for( int i = 0; i < 5; i++ )
{
futures.emplace_back( async(launch::async,
[=](){
timestamp(cout) << "Launch " << i << endl;
return i;
} ) );
}
this_thread::sleep_for( chrono::milliseconds(100) );
for( auto & f : futures ) timestamp(cout) << "Get " << f.get() << endl;
return 0;
}
输出(live example here):
[42us] Launch 4
[85us] Launch 3
[95us] Launch 2
[103us] Launch 1
[109us] Launch 0
[100134us] Get 0
[100158us] Get 1
[100162us] Get 2
[100165us] Get 3
[100168us] Get 4
这些操作微不足道,但如果您有长期 运行 任务,那么您可以预期,当您调用 std::future<T>::get()
时,部分或所有这些任务可能仍在执行。在这种情况下,您的线程将被挂起,直到与 future 关联的 promise 得到满足。此外,由于异步任务可能会合并,因此有些任务可能要等到其他任务完成后才会开始评估。
如果您改用 std::launch::deferred
,那么您将在调用线程上进行延迟计算,因此输出将类似于:
[100175us] Launch 0
[100323us] Get 0
[100340us] Launch 1
[100352us] Get 1
[100364us] Launch 2
[100375us] Get 2
[100386us] Launch 3
[100397us] Get 3
[100408us] Launch 4
[100419us] Get 4
[100430us] Launch 5
我创建了一个 for 循环来定义 std::future
的 std::vector
来执行我的函数 vector<int> identify
和另一个循环来通过调用 std::future::get()
来获取结果,如下所示:
for (int i = 0; i < NUM_THREADS; ++i) {
VecString::const_iterator first = dirList.begin() + i*share;
VecString::const_iterator last = i == NUM_THREADS - 1 ? dirList.end() : dirList.begin() + (i + 1)*share;
VecString job(first, last);
futures[i] = async( launch::async, [=]() -> VecInt {
return identify(i, job, make_tuple( bIDList, wIDList, pIDList, descriptor), testingDir, binaryMode, logFile, logFile2 );
} );
}
int correct = 0;
int numImages = 0;
for( int i = 0; i != NUM_THREADS; ++i ) {
VecInt ret = futures[i].get();
correct += ret[0];
numImages += ret[1];
}
工作是处理一些图像,我将工作大致平均分配给每个线程。我还在函数中嵌入 std::cout
以指示结果来自哪个线程。
我希望在第一个线程完成工作后,其他线程也应该完成他们的工作并且循环将打印出结果。但是,在第一个线程完成后,其他线程仍在工作。我认为它们确实有效,而不仅仅是打印结果,因为函数处理大图像时会有一些延迟。这让我想知道线程何时真正开始。
我从文档中知道每个线程在初始化后立即启动,但您如何解释我的观察结果?非常感谢,非常感谢您的帮助
通常,在未来模式中 get
通常会阻塞,直到结果设置为未来。这个结果也可以是一个例外。因此从另一个线程设置一个结果将解除阻塞获取。如果异常传播到未来,也会发生同样的情况。
这是 link 到 cppreference.com 描述的:
The get method waits until the future has a valid result and (depending on which template is used) retrieves it. It effectively calls wait() in order to wait for the result.
线程不能保证 运行 按照您创建它们的顺序,甚至按照应用程序分配的顺序。为它做。
可能会发生,你很幸运,等待未来,它会为最后一个排队的作业提供结果,因此所有其他作业都已完成,你不会注意到任何阻塞,但反之亦然.
std::async:不保证任何异步执行。这是 reference states:
The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a std::future that will eventually hold the result of that function call.
进一步说明:
If the async flag is set (i.e. policy & std::launch::async != 0), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned by std::thread(std::forward(f), std::forward(args)...), except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through the std::future that async returns to the caller.
您能否先尝试 运行 没有任何策略的 std::async
版本,它 should/might 重用内部线程池。如果它 运行s 更快,那么问题可能是应用程序没有重新使用线程?
最后,对 async 的引用有一个注释,说明何时 执行可以是同步的:
The implementation may extend the behavior of the first overload of std::async by enabling additional (implementation-defined) bits in the default launch policy. Examples of implementation-defined launch policies are the sync policy (execute immediately, within the async call) and the task policy (similar to async, but thread-locals are not cleared)
If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes, essentially making code such as the following synchronous:
std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes
调试多线程应用程序有点困难。我建议只创建一个额外的线程并使所有 job/futures 运行 通过它,看看在执行过程中是否存在一些误解。此时主线程不会打扰,因为它只是等待结果。
您还可以使用一些线程安全的日志库(例如 Boost Log)并记录那里发生的事情以及 std::async
通过注销线程 ID 创建了多少个不同的线程,如果这些线程完全被重新使用。
因为您正在使用 std::launch::async
,所以由 std::async
决定如何安排您的请求。根据cppreference.com:
The template function async runs the function f asynchronously (potentially in a separate thread which may be part of a thread pool) and returns a
std::future
that will eventually hold the result of that function call.
它确实保证它们将被线程化,但是,您可以推断,您的 lambda 的评估将安排在下一个可用的机会发生:
If the async flag is set (i.e.
policy & std::launch::async != 0
), then async executes the callable object f on a new thread of execution (with all thread-locals initialized) as if spawned bystd::thread(std::forward<F>(f), std::forward<Args>(args)...)
, except that if the function f returns a value or throws an exception, it is stored in the shared state accessible through thestd::future
that async returns to the caller.
但是,就您的问题而言,您只是想知道与您对 get
的调用相关的执行时间。很容易证明 get
与 std::launch::async
:
#include <iostream>
#include <future>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
int main() {
auto start = chrono::steady_clock::now();
auto timestamp = [start]( ostream & s )->ostream& {
auto now = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<chrono::microseconds>(now - start);
return s << "[" << elapsed.count() << "us] ";
};
vector<future<int>> futures;
for( int i = 0; i < 5; i++ )
{
futures.emplace_back( async(launch::async,
[=](){
timestamp(cout) << "Launch " << i << endl;
return i;
} ) );
}
this_thread::sleep_for( chrono::milliseconds(100) );
for( auto & f : futures ) timestamp(cout) << "Get " << f.get() << endl;
return 0;
}
输出(live example here):
[42us] Launch 4
[85us] Launch 3
[95us] Launch 2
[103us] Launch 1
[109us] Launch 0
[100134us] Get 0
[100158us] Get 1
[100162us] Get 2
[100165us] Get 3
[100168us] Get 4
这些操作微不足道,但如果您有长期 运行 任务,那么您可以预期,当您调用 std::future<T>::get()
时,部分或所有这些任务可能仍在执行。在这种情况下,您的线程将被挂起,直到与 future 关联的 promise 得到满足。此外,由于异步任务可能会合并,因此有些任务可能要等到其他任务完成后才会开始评估。
如果您改用 std::launch::deferred
,那么您将在调用线程上进行延迟计算,因此输出将类似于:
[100175us] Launch 0
[100323us] Get 0
[100340us] Launch 1
[100352us] Get 1
[100364us] Launch 2
[100375us] Get 2
[100386us] Launch 3
[100397us] Get 3
[100408us] Launch 4
[100419us] Get 4
[100430us] Launch 5