Edit: Sorry that I did something stupid. This question is wrong. Just go ahead to the answer or vote to close this question in case of wasting other's time.
I know that compilers and/or standard libaray are free to choose what policy to use for std::async
with default policy. But how do they determine that?
This program has really confusing behavior.
// extern.cpp
#include <future>
extern std::future<void> a;
void task(int t);
void extern_func1() {
a.get();
}
void extern_func2() {
task(2);
}
// main.cpp
#include <chrono>
#include <future>
#include <thread>
#include <iostream>
#include <sstream>
#include <mutex>
std::mutex m;
auto now_sec() {
using namespace std::chrono;
auto t = system_clock::now().time_since_epoch();
return duration_cast<seconds>(t).count();
}
template<class...Args>
void log(Args&&...args) {
std::ostringstream oss;
oss << "thread#" << std::this_thread::get_id() << " "
<< "time:" << now_sec() << " ";
(..., (oss << args << " "));
oss << "\n";
std::string msg = oss.str();
std::unique_lock l{m};
std::cout << msg;
std::cout.flush();
}
void task(int t) {
log("task", t, "begin");
using namespace std::chrono_literals;
std::this_thread::sleep_for(3s);
log("task", t, "end");
}
std::future<void> a;
void extern_func1();
void extern_func2();
int main() {
auto t1 = now_sec();
a = std::async(task, 1);
#if 1
extern_func1(); // <---- This order uses "deferred" policy(See below)
extern_func2();
#else
extern_func2(); // <---- This order uses async policy
extern_func1();
#endif
auto t2 = now_sec();
log("total", t2 - t1, "secs");
}
If extern_func1
is called before extern_func2
, the program use "deferred" policy and outputs
thread#0x16da17000 time:1648037227 task 1 begin
thread#0x16da17000 time:1648037230 task 1 end
thread#0x1026e0580 time:1648037230 task 2 begin
thread#0x1026e0580 time:1648037233 task 2 end
thread#0x1026e0580 time:1648037233 total 6 secs
It's not the standard deferred policy because a new thread is spawned. But it starts when result is requested.
If If extern_func2
is called before extern_func1
, the program use async policy and outputs
thread#0x102970580 time:1648037352 task 2 begin
thread#0x16d80f000 time:1648037352 task 1 begin
thread#0x102970580 time:1648037355 task 2 end
thread#0x16d80f000 time:1648037355 task 1 end
thread#0x102970580 time:1648037355 total 3 secs
The order is the only diference. Why the order of calling external functions of exactly same signature change the used policy or the thread scheduling? How is this possible?
Originall there's only one translation unit. In order to rule out compiler's optimization, I moved the two functions into another translation unit and compiled them with -fno-lto
to disable link-time optimization. But still, both GCC and Clang gives this confusing behavior.
Clang's version:
Homebrew clang version 13.0.1
Target: arm64-apple-darwin21.1.0
Thread model: posix
InstalledDir: /opt/homebrew/opt/llvm/bin
GCC's version
g (Homebrew GCC 11.2.0_3) 11.2.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
CodePudding user response:
There is no policy change. Look at extern_func1/2
again:
void extern_func1() {
a.get();
}
void extern_func2() {
task(2);
}
It's simply that when you call extern_func1
first you are running:
a = std::async(task, 1); // start task 1
a.get(); // wait for task 1 to finish
task(2); // run task 2 on main thread
and when you call extern_func2
first you are running:
a = std::async(task, 1); // start task 1
task(2); // run task 2 on main thread
a.get(); // wait for task 1 to finish
When you call extern_func1
first, you are preventing both tasks from running at the same time! You are telling the computer not to run task 2 until task 1 finishes, and the computer is doing exactly what you tell it to. No weirdness here!