C++ std::async and std::future: Simplifying Asynchronous Programming!

Hi, friends! I’m Hui Mei! 😊 Today we are going to talk about a very useful tool in C++ — **std::async and std::future**, which make asynchronous programming simple and efficient. If you’ve ever struggled with issues like locks and callback functions in multithreaded programming, today’s content will surely open up a new world for you!

With the help of the C++ standard library’s std::async and std::future, you can easily launch asynchronous tasks and retrieve the results when needed. This approach not only makes your code cleaner but also helps avoid common multithreading pitfalls, such as resource contention and deadlocks.

Today we will learn from the following aspects:

  1. What is asynchronous programming? The basic concepts of std::async and std::future.
  2. How to use them to launch asynchronous tasks and obtain results?
  3. Practical application scenarios and code examples.
  4. Common questions and precautions.

Are you ready? Let’s get started! 🚀

1. What is Asynchronous Programming?

1. Asynchronous Programming vs Synchronous Programming

In traditional synchronous programming, the program waits for a task to complete before continuing execution. For example, suppose you are doing two things: boiling water and chopping vegetables. If executed synchronously, you must wait for the water to boil before you can start chopping.

In asynchronous programming, the program can execute multiple tasks simultaneously. For example, you can let the boiling water task run in the background while you continue chopping vegetables in the foreground. This not only increases efficiency but also makes full use of system resources.

In C++, std::async is a key tool for implementing asynchronous programming. It can start an asynchronous task and return a std::future object for retrieving the task’s execution result.

2. Basic Concepts of std::async and std::future

  • **std::async**:

    • A function template used to launch asynchronous tasks.
    • Asynchronous tasks can run in independent threads or can be executed later in the calling thread.
  • **std::future**:

    • Used to store the results of asynchronous tasks.
    • The result can be obtained through the get() method. If the task is not completed, get() will block the calling thread until the result is available.

2. How to Use std::async and std::future?

1. Basic Usage: Launching Asynchronous Tasks and Obtaining Results

Here’s a simple example demonstrating how to use std::async to launch an asynchronous task and obtain its result through std::future:

#include <iostream>
#include <future> // Include std::async and std::future

// A simple function to simulate a time-consuming task
int computeSquare(int x) {
    std::cout << "Calculating..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate a time-consuming operation
    return x * x;
}

int main() {
    // Use std::async to launch an asynchronous task
    std::future<int> result = std::async(computeSquare, 5);

    std::cout << "Asynchronous task started, main thread can continue with other operations!" << std::endl;

    // Call get() to obtain the result of the asynchronous task
    int square = result.get();
    std::cout << "Calculation result:" << square << std::endl; // Output: 25

    return 0;
}

Output:

The asynchronous task has started, the main thread can continue with other operations!
Calculating...
Calculation result: 25

2. Code Analysis

  1. std::async starts the asynchronous task:

  • std::async(computeSquare, 5) starts an asynchronous task that calls computeSquare(5).
  • The return value is a std::future<int> that stores the task’s return result.
  • Obtaining the Result of the Asynchronous Task:

    • Calling result.get() will block the current thread until the asynchronous task completes and returns a result.
    • get() can only be called once, after which it will release the task’s result.
  • Main Thread Continues Execution:

    • While the asynchronous task is running, the main thread can continue executing other operations, only calling get() when the result is needed.

    3. Controlling Task Execution Strategies

    std::async supports two task execution strategies:

    • **std::launch::async**: Forces the task to execute asynchronously in a new thread.
    • **std::launch::deferred**: The task is executed only when get() or wait() is called.

    Example: Specifying Execution Strategy

    #include <iostream>
    #include <future>
    #include <thread>
    
    int compute(int x) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return x * 2;
    }
    
    int main() {
        // Force asynchronous execution
        std::future<int> asyncResult = std::async(std::launch::async, compute, 10);
    
        // Deferred execution
        std::future<int> deferredResult = std::async(std::launch::deferred, compute, 20);
    
        std::cout << "Main thread continues to execute..." << std::endl;
    
        // Get asynchronous task results
        std::cout << "Asynchronous result:" << asyncResult.get() << std::endl; // Output: 20
        std::cout << "Deferred result:" << deferredResult.get() << std::endl; // Output: 40
    
        return 0;
    }
    

    Output:

    Main thread continues to execute...
    Asynchronous result: 20
    Deferred result: 40
    

    3. Practical Application Scenarios

    1. Parallel Computing Tasks

    When multiple time-consuming tasks need to be run, std::async can be used to allocate them to different threads for execution, greatly enhancing program performance.

    Example: Parallel Computing Multiple Tasks

    #include <iostream>
    #include <future>
    
    int task1() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        return 10;
    }
    
    int task2() {
        std::this_thread::sleep_for(std::chrono::seconds(3));
        return 20;
    }
    
    int main() {
        // Launch two asynchronous tasks
        std::future<int> result1 = std::async(task1);
        std::future<int> result2 = std::async(task2);
    
        std::cout << "Waiting for tasks to complete..." << std::endl;
    
        // Get task results
        int sum = result1.get() + result2.get();
        std::cout << "Total result:" << sum << std::endl; // Output: 30
    
        return 0;
    }
    

    Output:

    Waiting for tasks to complete...
    Total result: 30
    

    2. Asynchronous File Processing

    Suppose we need to read the contents of multiple files simultaneously; we can use std::async to process each file in parallel, enhancing reading efficiency.

    4. Tips and Precautions

    Tips

    1. Avoid Shared Data Contention:

    • If asynchronous tasks need to access shared data, ensure to use std::mutex or other synchronization mechanisms to avoid data contention.
  • Checking the Status of std::future:

    • Before calling get(), you can check if the future contains a valid result using valid():

      if (result.valid()) {
          int value = result.get();
      }
      
  • Exception Handling:

    • If an asynchronous task throws an exception, calling get() will rethrow it, which can be caught using try-catch.

    Common Questions

    Q1: What is the difference between std::async and manually creating threads? A1: std::async is higher level and automatically manages the thread’s lifecycle, whereas manually creating threads requires the developer to manage it themselves.

    Q2: Can get() be called multiple times? A2: No! get() can only be called once, after which it will release the task’s result. If you need to access the result multiple times, you can store it in a variable.

    5. Exercises

    1. Use std::async to start three asynchronous tasks to calculate the sum from 1 to 100, from 101 to 200, and from 201 to 300, and output the total sum.
    2. Modify the parallel computing example to use the specified std::launch::async execution strategy.
    3. Simulate an asynchronous file download task, outputting ‘Download Complete’ after the task finishes.

    6. Summary

    Today we learned about **std::async and std::future** in C++, which are powerful tools for asynchronous programming, enabling easy parallel task processing while ensuring code simplicity and maintainability. Through code examples and exercises, I believe you have mastered their basic usage and practical application scenarios.

    Friends, that’s it for today’s C++ learning journey! Remember to practice hands-on, and feel free to ask me any questions in the comments. I wish everyone a happy learning experience and continuous improvement in C++ skills! 🎉

    Leave a Comment