[C++11] make multithreading development simple -- asynchronous operation

Posted by xmatthawkx on Tue, 16 Nov 2021 08:27:40 +0100

C++ 11 provides classes and functions related to asynchronous operations. Different classes have different functions. Generally speaking, classes mainly include: STD:: future, STD:: promise, STD:: package_ The task function is mainly std::async.

1 asynchronous operation class

The main functions of the above three classes are:

  • std::future: this class is mainly used as an asynchronous result transmission channel to obtain the return value of thread function;
  • std::promise: used to wrap a value and bind it with futre to facilitate thread assignment;
  • std::package_task: wrap a callable object and use it with future to facilitate asynchronous invocation.

1.1 std::future

The thread library provides the result of the future access asynchronous operation. Because the asynchronous result cannot be obtained immediately, it can only be obtained at some time in the future. This result is a future value, so it is called future. The results are obtained by querying the future status. Future consists of three statuses:

  • Deferred: the asynchronous operation has not started yet
  • Ready: the asynchronous operation has completed
  • Timeout: asynchronous operation timeout

In actual coding, the execution results can be obtained asynchronously by judging these three internal states. Code examples are as follows:

std::cout << "checking, please wait";
  std::chrono::milliseconds span (1000);
  do{
      status = fut.wait_for(span);
      if(status == std::future_status::deferred)
      {
          std::cout << "\ndeferred" << std::flush;
      }
      else if(status == std::future_status::timeout)
      {
          std::cout << "\ntimeout" << std::flush;
      }
      else if(status == std::future_status::ready)
      {
          std::cout << "\ready" << std::flush;
      }
  }while(status != std::future_status::ready);

1.2 std::promise

Using std::promise, you can transfer values between two different threads. The method of use is as follows:

void print_set(std::promise<int> &pro)
{
    std::cout << "set value: " << "100" << '\n';
    pro.set_value(100);
}

void print_int (std::future<int>& fut) {
  int x = fut.get();
  std::cout << "value: " << x << '\n';
}

int main ()
{
  std::promise<int> prom;                  
  std::future<int> fut = prom.get_future();     
  std::thread th2 (print_set, std::ref(prom));  
  std::thread  th1 (print_int, std::ref(fut));                                                
  th1.join();
  th2.join();
  return 0;
}

The running results of the above code are as follows:

set value: 100
value: 100

1.3 std::package_task

Similar to std::promise, it needs to be used in combination with future, but the difference is std::package_task wraps a function to facilitate asynchronous calls. The method of use is as follows:

int Add(int x,int y)
{
    return (x+y);
}

int main ()
{
  std::packaged_task<int(int,int)> pFun(Add);              
  std::future<int> fut = pFun.get_future();     
  std::thread th2 (std::move(pFun), 3,2);  
  int iRes = fut.get();
  std::cout<<"iRes="<<iRes<<std::endl;
  th2.join();
  return 0;
}

The running result of the program is: iRes=5

2. Asynchronous operation function: async

Compared with the previous asynchronous operation classes, std::async is much more advanced, and you can directly create asynchronous task classes. The asynchronous return results are saved in future. When obtaining the return results of thread functions, use get() to obtain the return value. If no value is required, use wait() method.

The std::async prototype is as follows:

future<typename result_of<Fn(Args...)>::type>
    async (launch::async|launch::deferred, Fn&& fn, Args&&... args);

The prototype parameters are described as follows:

  • launch::async: start the thread after calling async
  • launch::deferred: delay starting the thread until wait and get are called later.
  • fn: thread function
  • args: thread function parameters

The basic usage of async is as follows:

bool is_prime (int x) {
  std::this_thread::sleep_for(std::chrono::seconds(3));
  for (int i=2; i<x; ++i) if (x%i==0) return false;
  return true;
}

int main ()
{
  // call function asynchronously:
  std::future<bool> fut = std::async (is_prime,444444443); 
  std::future_status status;
  // do something while waiting for function to set future:
  std::cout << "checking, please wait";
  std::chrono::milliseconds span (1000);
  do{
      status = fut.wait_for(span);
      if(status == std::future_status::deferred)
      {
          std::cout << "\ndeferred" << std::flush;
      }
      else if(status == std::future_status::timeout)
      {
          std::cout << "\ntimeout" << std::flush;
      }
      else if(status == std::future_status::ready)
      {
          std::cout << "\ready" << std::flush;
      }
  }while(status != std::future_status::ready);
  
  bool x = fut.get();     // retrieve return value

  std::cout << "\n444444443 " << (x?"is":"is not") << " prime.\n";

  return 0;
}

If the operation of the above code is:

checking, please wait
"\n444444443 is prime."

Async is a higher-level asynchronous operation. In actual coding, you can easily obtain the thread execution status and results without paying attention to the details of thread creation; Generally, the launch::async parameter is used by default.