C++ 中 std::async 和 std::future 的并发性

并发是现代软件开发中的一个强大概念,它允许同时执行多个任务,从而提高应用程序的整体性能和响应能力。 C++ 提供了多种工具和库来利用并发的力量,其中两个最基本的组件是 std::async 和 std::future。在本文中,我将通过一个简单的示例来解释这两个功能及其实际应用。

在上图中,我们看到一架飞机在日落时分在傍晚的天空中翱翔,代表了并发的概念。正如飞机的发动机、导航系统和飞行控制系统无缝协同工作以确保安全高效的旅程一样,并发编程使不同的线程或任务能够和谐地协同工作,每个线程或任务都发挥其独特的作用。

C++11 引入了 标头,它提供了 std::future 和 std::promise 等类来管理异步操作和线程之间的通信。 std::async 是启动异步任务并使用 std::future 获取其结果的便捷方法。

为了更好地理解 std::async 和 std::future 的工作原理,让我们考虑一个示例场景,其中我们需要同时分块下载大文件。这是我们将在整篇文章中使用的代码的简化版本:

cpp 复制代码
#include <iostream>
#include <vector>
#include <future>
#include <fstream>
#include <mutex>

constexpr int totalFileSize = 1000000;
constexpr int chunkSize = 100000;
std::vector<char> fileData(totalFileSize);
std::mutex fileMutex;

int downloadChunk(int startIndex) {
    // Simulate downloading a chunk of data from the internet
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    
    std::lock_guard<std::mutex> lock(fileMutex);
    for (int i = 0; i < chunkSize; ++i) {
        fileData[startIndex + i] = 'X'; // Mark downloaded part
    }

    std::cout << "\nDownloaded block: " << startIndex <<"-"<< startIndex+chunkSize << std::endl;

    return (startIndex+chunkSize);
}

int downloadAndWait() {
    // ... (content will be added later)
}


int downloadNonBlocking()
{
    // ... (content will be added later)
}

int main() 
{
    downloadAndWait();
    downloadNonBlocking();
}

在此代码中,我们有两种实现:一种等待所有任务完成 (downloadAndWait() ),另一种以非阻塞方式监视任务完成 (downloadNonBlocking() )。

在第一个实现(downloadAndWait)中,std::async用于同时启动下载任务。 std::launch::async 策略确保每个任务在其自己的线程中运行。 std::async 返回的 std::future 对象允许我们跟踪进度并检索结果。

cpp 复制代码
int downloadAndWait() {
    // Imagine a big file waiting to be downloaded.
    std::cout << "Downloading file...\n";

    std::vector<std::future<int>> downloadTasks;

    // Divide the file into chunks and start downloading concurrently
    for (int i = 0; i < totalFileSize; i += chunkSize) {
        downloadTasks.push_back(std::async(std::launch::async, downloadChunk, i));
    }

    // Wait for all download tasks to complete
    for (auto& task : downloadTasks) {
        task.wait();
    }

    // Imagine the downloaded file is ready to be used.
    std::cout << "File downloaded.\n";

    // Check if the entire file is downloaded
    bool isFileComplete = true;
    for (char c : fileData) {
        if (c != 'X') {
            isFileComplete = false;
            break;
        }
    }

    if (isFileComplete) {
        std::cout << "Downloaded file is complete.\n";
    } else {
        std::cout << "Downloaded file is incomplete.\n";
    }

    return 0;
}

该函数遵循一种简单的方法来同时下载文件:

  1. 它初始化一个由 std::future 对象组成的向量,名为 downloadTasks ...
  2. 在循环中,它将文件分成块并使用 std::async 启动每个块的下载任务。 std::launch::async 策略确保每个任务同时运行。
  3. 启动所有任务后,该函数进入另一个循环,等待每个任务使用 std::future 对象上的 wait() 方法完成。
  4. 所有任务完成后,该函数将通过验证 fileData 矢量的内容来检查下载的文件是否完整。

在第二个实现(downloadNonBlocking)中,我们使用非阻塞方法来监视任务完成情况。我们使用 wait_for() 不断检查每个任务的状态,以避免阻塞主线程。当任务完成时,我们使用 task->get() 检索其结果。

cpp 复制代码
int downloadNonBlocking()
{

    // Imagine a big file waiting to be downloaded.
    std::cout << "Downloading file...\n";

    using DownloadTaskList = std::vector<std::future<int>>;
    DownloadTaskList downloadTasks;

    // Divide the file into chunks and start downloading concurrently
    for (int i = 0; i < totalFileSize; i += chunkSize) {
        downloadTasks.push_back(std::async(std::launch::async, downloadChunk, i));
    }

    while (!downloadTasks.empty())
    {
         std::cout << "loop begin[";
        DownloadTaskList::iterator task = downloadTasks.begin();
        while (task != downloadTasks.end())
        {
            std::future_status fStat = task->wait_for(std::chrono::milliseconds(1));
            if (fStat == std::future_status::timeout)
            {
                std::cout << ".";
                ++task;
            }
            else {
                try {
                    //std::cout << "Downloaded file block end pos: "<< task->get() << '\n';
                }
                catch (const std::exception& e) {
                    std::cout << "Exception " << e.what() << '\n';
                }
                task = downloadTasks.erase(task);
            }
        }
        std::cout << "] loop end" << std::endl;
    }

    // Imagine the downloaded file is ready to be used.
    std::cout << "File downloaded.\n";

    // Check if the entire file is downloaded
    bool isFileComplete = true;
    for (char c : fileData) {
        if (c != 'X') {
            isFileComplete = false;
            break;
        }
    }

    if (isFileComplete) {
        std::cout << "Downloaded file is complete.\n";
    } else {
        std::cout << "Downloaded file is incomplete.\n";
    }

    return 0;
}

该函数采用非阻塞的方式来监控和管理并发下载任务:

  1. 它初始化 downloadTasks 矢量并启动文件每个块的下载任务,类似于之前的实现。
  2. 该函数进入一个循环,只要 downloadTasks 向量中存在任务,该循环就会进行迭代。
  3. 在循环内,它使用迭代器迭代任务列表。对于每个任务,它使用 wait_for() 检查其状态。如果状态为 std::future_status::timeout ,则意味着任务仍在运行,因此迭代器会递增。
  4. 如果任务的状态指示完成,则迭代器前进到下一个任务,并使用 erase() 方法从 downloadTasks 向量中删除已完成的任务。
  5. 该函数继续循环,直到完成所有任务。

downloadAndWait() 的控制台输出如下所示:

bash 复制代码
Downloading file (downloadAndWait)...
Downloaded block: 300000-400000
Downloaded block: 200000-300000
Downloaded block: 0-100000
Downloaded block: 100000-200000
Downloaded block: 400000-500000
Downloaded block: 500000-600000
Downloaded block: 600000-700000
Downloaded block: 700000-800000
Downloaded block: 800000-900000
Downloaded block: 900000-1000000
File downloaded.
Downloaded file is complete.

downloadNonBlocking() 的控制台输出如下所示:

bash 复制代码
Downloading file (downloadNonBlocking)...
loop begin[..........] loop end
loop begin[..........] loop end
loop begin[..........] loop end
loop begin[..........] loop end
loop begin[..........] loop end
loop begin[..........] loop end
loop begin[.......
Downloaded block: 800000-900000
..] loop end
loop begin[
Downloaded block: 600000-700000
..
Downloaded block: 400000-500000
..
Downloaded block: 300000-400000
.
Downloaded block: 0-100000
..] loop end
loop begin[
Downloaded block: 900000-1000000
.
Downloaded block: 700000-800000
..] loop end
loop begin[
Downloaded block: 100000-200000
..] loop end
loop begin[
Downloaded block: 500000-600000
.] loop end
loop begin[.] loop end
loop begin[
Downloaded block: 200000-300000
] loop end
File downloaded.
Downloaded file is complete.

两种实现都实现了使用 std::async 和 std::future 并行下载块文件的目标。第一个实现等待所有任务完成后再继续,而第二个实现使用非阻塞方法来监视任务完成情况而不阻塞主线程。使用 std::mutex 可确保访问共享资源时的数据完整性。根据您项目的具体要求,您可以选择最适合您需求的方法。

相关推荐
It's now1 小时前
Spring Framework 7.0 原生弹性功能系统讲解
java·后端·spring
不会代码的小猴1 小时前
C++的第九天笔记
开发语言·c++·笔记
一 乐2 小时前
人事管理系统|基于Springboot+vue的企业人力资源管理系统设计与实现(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·spring boot·后端
带刺的坐椅2 小时前
Solon AI 开发学习19 - 结合 Solon Flow 实现 ReAct 效果
java·ai·chatgpt·llm·openai·solon·deepseek
CoderYanger2 小时前
Java SE——12.异常(≠错误)《干货笔记》
java·开发语言
Data_agent2 小时前
1688获得1688店铺所有商品API,python请求示例
java·开发语言·python
一晌小贪欢2 小时前
【Python办公】-图片批量添加文字水印(附代码)
开发语言·python·图片水印·python水印·python添加水印·图片添加水印
why1512 小时前
面经整理——算法
java·数据结构·算法
Yeats_Liao2 小时前
CANN Samples(十三):Ascend C 算子开发入门
c语言·开发语言