并发是现代软件开发中的一个强大概念,它允许同时执行多个任务,从而提高应用程序的整体性能和响应能力。 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;
}
该函数遵循一种简单的方法来同时下载文件:
- 它初始化一个由 std::future 对象组成的向量,名为 downloadTasks ...
- 在循环中,它将文件分成块并使用 std::async 启动每个块的下载任务。 std::launch::async 策略确保每个任务同时运行。
- 启动所有任务后,该函数进入另一个循环,等待每个任务使用 std::future 对象上的 wait() 方法完成。
- 所有任务完成后,该函数将通过验证 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;
}
该函数采用非阻塞的方式来监控和管理并发下载任务:
- 它初始化 downloadTasks 矢量并启动文件每个块的下载任务,类似于之前的实现。
- 该函数进入一个循环,只要 downloadTasks 向量中存在任务,该循环就会进行迭代。
- 在循环内,它使用迭代器迭代任务列表。对于每个任务,它使用 wait_for() 检查其状态。如果状态为 std::future_status::timeout ,则意味着任务仍在运行,因此迭代器会递增。
- 如果任务的状态指示完成,则迭代器前进到下一个任务,并使用 erase() 方法从 downloadTasks 向量中删除已完成的任务。
- 该函数继续循环,直到完成所有任务。
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 可确保访问共享资源时的数据完整性。根据您项目的具体要求,您可以选择最适合您需求的方法。