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 可确保访问共享资源时的数据完整性。根据您项目的具体要求,您可以选择最适合您需求的方法。

相关推荐
+VX:Fegn089515 小时前
计算机毕业设计|基于springboot + vueOA工程项目管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
郝学胜-神的一滴15 小时前
Linux进程与线程控制原语对比:双刃出鞘,各显锋芒
linux·服务器·开发语言·数据结构·c++·程序人生
JasmineWr15 小时前
Spring事务解析
java·spring
小钟不想敲代码15 小时前
Python(一)
开发语言·python
ji_shuke15 小时前
canvas绘制拖拽箭头
开发语言·javascript·ecmascript
qq_3363139315 小时前
java基础-IO流(缓冲流)
java·开发语言
青岛少儿编程-王老师15 小时前
CCF编程能力等级认证GESP—C++2级—20251227
java·开发语言·c++
沐知全栈开发16 小时前
jQuery 杂项方法
开发语言
高山上有一只小老虎16 小时前
小红的推荐系统
java·算法
javachen__16 小时前
341-十道经典程序设计题目
数据结构·c++·算法