C++ library std::future

std::future

  • introduce
    • member function
    • effect
  • scenes to be used
    • asynchronous task
    • concurrency control
    • result acquisition
  • usage example
    • Associate asynchronous tasks with std::async
    • Using std::promise with std::future
    • Result acquisition and exception handling
  • Precautions
  • other
    • std::shared_future
    • std::future_status

Introduction

std::futuref

std::future is a template class in the C++11 Standard Library (Concurrency Support Library) that represents the result of an asynchronous operation. When we use asynchronous tasks in multi-threaded programming, std::future can help us get the execution results of tasks when needed. An important feature of std::future is the ability to block the current thread until the asynchronous operation completes, thus ensuring that we do not encounter outstanding operations while fetching results.

Member function

  • Constructor
    • future() noexcept: Default constructor. Constructs a std::future with no shared state. After construction, valid() == false
    • future( future & amp; & amp; other ) noexcept: Move constructor. Constructs a std::future with move semantics from the shared state of other. other.valid() == false after construction.
    • future( const future & amp; other ) = delete : CopyConstructible is not possible.
  • Destructor ~future();
  • operator=
    • future & amp; operator=( future & amp; & amp; other ) noexcept: releases any shared state and moves the contents of the assignment other to *this . After assignment, other.valid() == false and this->valid() will yield the same value as other.valid() before assignment.
    • future & amp; operator=( const future & amp; other ) = delete: CopyAssignable is not possible.
  • share() noexcept: Transfers the shared state of *this to the std::shared_future object. Multiple std::shared_future objects can refer to the same shared object, after calling valid() == false.
  • T get(): The get method waits until the future has a valid result and (depending on which template is used) fetches it. It equivalently calls wait() to wait for the result. The generic template and the two template specializations each have a single get version. The three versions of get differ only in the return type. The behavior is undefined if valid() is false before calling this function. Release any shared state. valid() is false after calling this method.
  • bool valid(): Check whether the future can share state.
  • wait(): wait for results to become available
  • wait_for
  • wait_until

Function

  1. Getting the result of an asynchronous operation: std::future provides a mechanism that allows us to safely get the result of an asynchronous operation in a multi-threaded environment.
  2. Hide the details of asynchronous operations: std::future encapsulates the results of asynchronous operations, so that programmers do not need to pay attention to the specific implementation details of thread synchronization and communication.
  3. Thread Synchronization: By blocking and waiting for an asynchronous operation to complete, std::future can ensure that we have obtained the desired result before continuing to perform other operations.
  4. Exception handling: std::future can catch exceptions thrown in asynchronous operations and rethrow them when getting results, or handle them in the main thread , which makes exception handling easier.
  5. Improve performance: std::future allows us to better utilize the performance of multi-core processors and improve the execution efficiency of programs by executing tasks in parallel.

Usage scenario

Asynchronous task

When we need to perform some time-consuming operations in the background, such as file read and write, network requests or computationally intensive tasks, std::future can be used to represent the results of these asynchronous tasks. By separating tasks from the main thread, we can achieve parallel processing of tasks, thereby improving the execution efficiency of the program.

Concurrency control

In multi-threaded programming, we may need to wait for some tasks to complete before proceeding with other operations. By using std::future, we can achieve synchronization between threads to ensure that the task is completed before getting the result and continuing to perform subsequent operations.

Get the results

std::future provides a safe way to get the result of an asynchronous task. We can use the std::future::get() function to get the result of the task. This function blocks the current thread until the asynchronous operation completes. In this way, when calling the get() function, we can ensure that the desired result has been obtained. If an exception occurs in an asynchronous operation, the get() function will rethrow the exception, allowing us to handle these exceptions.

Usage example

header file:

#include <future>

Using std::async to associate asynchronous tasks

std::async is a simple way to associate a task with a std::future. It creates and runs an asynchronous task and returns a std::future object associated with the task result.

#include 
#include <future>
#include 

int long_running_task() {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 42;
}

int main() {
    std::future result_future = std::async(std::launch::async, long_running_task);

    // perform other operations here

    int result = result_future. get();
    std::cout << "Result: " << result << std::endl;

    return 0;
}

Using std::promise with std::future

std::promise is another way to work with std::future. We can use the std::promise object to explicitly set the result of the task, and the std::future object is used to get the result.

#include 
#include <future>
#include 

void long_running_task(std::promise result_promise) {
    // Execute the long running task
    int result = 42;

    // Set the result to the promise object
    result_promise.set_value(result);
}

int main() {
    std::promise result_promise;
    std::future result_future = result_promise. get_future();

    // create a new thread to execute the long running task
    std::thread task_thread(long_running_task, std::move(result_promise));

    // perform other operations here

    int result = result_future. get();
    std::cout << "Result: " << result << std::endl;

    task_thread. join();

    return 0;
}

Result acquisition and exception handling

Use the std::future::get() function to get the result of an asynchronous task. This function blocks the current thread until the asynchronous operation completes. If an exception occurs during an asynchronous operation, the get() function will rethrow the exception.

// Note: The std::future::get() function can only be called once. After calling get(), the std::future object becomes invalid.
// If you need to access the result multiple times, consider using std::shared_future.
try {<!-- -->
    int result = result_future. get();
    std::cout << "Result: " << result << std::endl;
} catch (const std::exception & e) {<!-- -->
    std::cerr << "Exception caught: " << e.what() << std::endl;
} catch (...) {<!-- -->
    std::cerr << "Unknown exception caught" << std::endl;
}

std::chrono::milliseconds timeout(100);
std::future_status status = result_future.wait_for(timeout);

if (status == std::future_status::ready) {<!-- -->
    int result = result_future. get();
    std::cout << "Result: " << result << std::endl;
} else {<!-- -->
    std::cerr << "Timeout: the task is still running" << std::endl;
}

In order to avoid blocking, we can use the std::future::wait_for() or std::future::wait_until() function to wait for a period of time or until a certain moment. These functions return a std::future_status enumeration value indicating the status of the asynchronous operation (std::future_status::ready, std::future_status:: timeout or std::future_status::deferred).

Notes

When using std::future, you need to pay attention to the following points:

  1. The std::future::get() function can only be called once. After calling get(), the std::future object becomes invalid. If you need to access the result multiple times, consider using std::shared_future.
  2. std::future objects cannot be copied, but can be transferred through the move constructor or move assignment operator. This means we cannot store std::future objects in containers unless wrapped with std::shared_future or pointers.
  3. When using std::async, tasks may be executed in the current thread context by default. It depends on library implementation and system resources. To ensure that tasks are executed in a new thread, the std::launch::async flag can be used:

std::future result_future = std::async(std::launch::async, long_running_task);

  1. If the std::future object is still associated with an active asynchronous operation when it is destructed, and the operation has not yet completed, the destructor will block waiting for the operation to complete. In some cases, this can cause the program to deadlock. To avoid this problem, you can explicitly call the wait(), wait_for(), or wait_until() functions before destructing the std::future object.
  2. Although std::future is very useful for obtaining asynchronous task results and thread synchronization, it cannot solve all concurrency problems. For example, std::future cannot be used to implement complex concurrency patterns such as thread pools, work stealing, etc. For these advanced concurrency requirements, additional libraries or custom implementations may be required.
  3. When using std::promise and std::future, you need to ensure that the corresponding value has been set before calling get(), otherwise it will results in undefined behavior. This may require careful design and debugging of the code to ensure the correct order of execution.
  4. The lifetime of a std::future object needs to be carefully managed. If a std::future object is destroyed prematurely, the associated asynchronous task may become inaccessible. Therefore, it is necessary to ensure that the validity of the std::future object is maintained until the asynchronous task is completed.
  5. In some cases, using std::future may cause performance degradation. For example, when multiple threads are frequently waiting for the result of an asynchronous operation, this can result in thread blocking and context switching overhead. In order to avoid this problem, you can consider using a non-blocking way to query the status of asynchronous operations, such as std::future::wait_for() and std::future::wait_until() function. In this way, we can perform other tasks while the asynchronous operation is not completed, improving the responsiveness and concurrency performance of the program.
  6. std::future is not suitable for all scenarios. For example, it is not suitable for tasks that need to process multiple inputs or output results, or scenarios that need to implement dynamic task dependencies. In these cases, it may be necessary to look for other concurrency and synchronization solutions.
  7. Although std::future provides an exception handling mechanism, it should be noted that once the std::future::get() function rethrows an exception, the exception requires Capture and process in the thread that calls get(). This means that appropriate exception handling strategies need to be set in the main thread and other threads to ensure the stability and robustness of the program.

Other

std::shared_future

std::shared_future is a variant of std::future that allows multiple threads to share the results of the same asynchronous operation. Unlike std::future, std::shared_future objects can be copied, so they can be stored in containers or passed between multiple threads. Additionally, the std::shared_future::get() function can be called multiple times without making the std::shared_future object invalid.

#include 
#include <future>
#include 

void print_result(std::shared_future result_future) {
    int result = result_future. get();
    std::cout << "Result: " << result << std::endl;
}

int main() {
    std::promise result_promise;
    std::shared_future result_future = result_promise. get_future(). share();

    std::thread t1(print_result, result_future);
    std::thread t2(print_result, result_future);

    result_promise. set_value(42);

    t1. join();
    t2. join();

    return 0;
}

std::future_status

std::future_status is an enumeration type, indicating the status of asynchronous operations. It has three possible values:

  • std::future_status::ready: The asynchronous operation has completed and the result is available.
  • std::future_status::timeout: The asynchronous operation has not yet completed, and the wait has timed out.
  • std::future_status::deferred: the asynchronous operation has been deferred and has not yet started (only if the std::launch::deferred strategy was used to create a std::future object).

We can use the std::future::wait_for() and std::future::wait_until() functions to query the status of an asynchronous operation without blocking the current thread.

#include 
#include <future>
#include 

int long_running_task() {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return 42;
}

int main() {
    std::future result_future = std::async(std::launch::async, long_running_task);

    std::chrono::milliseconds timeout(1000);
    std::future_status status = result_future.wait_for(timeout);

    if (status == std::future_status::ready) {
        int result = result_future. get();
        std::cout << "Result: " << result << std::endl;
    } else {
        std::cerr << "Timeout: the task is still running" << std::endl;
    }
// wait for the task to complete so that the result can be retrieved correctly
int result = result_future. get();
std::cout << "Final result: " << result << std::endl;

return 0;
}

In the above example, we used the std::future::wait_for() function to query the status of the asynchronous operation. If the status is std::future_status::ready, we can get the result immediately. Otherwise, we can perform other tasks during the waiting period to improve the responsiveness and concurrency performance of the program. Finally, before exiting the main function, we call result_future.get() to ensure that the result of the asynchronous operation is correctly retrieved.