Home > Blockchain >  How does compiler/stdlib determine what policy to use for default std::async
How does compiler/stdlib determine what policy to use for default std::async

Time:03-24

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!

  •  Tags:  
  • c
  • Related