【C++实战(54)】C++11新特性实战:解锁原子操作与异步编程的奥秘

目录

  • 一、原子操作的概念与实战
    • [1.1 原子类型(std::atomic)的定义与常用操作(load、store、exchange)](#1.1 原子类型(std::atomic)的定义与常用操作(load、store、exchange))
    • [1.2 原子操作的内存序(memory order):relaxed、acquire - release、seq_cst](#1.2 原子操作的内存序(memory order):relaxed、acquire - release、seq_cst)
    • [1.3 原子操作与互斥锁的性能对比](#1.3 原子操作与互斥锁的性能对比)
  • 二、异步编程模型(C++11)
    • [2.1 std::future 与 std::promise 的使用(线程间结果传递)](#2.1 std::future 与 std::promise 的使用(线程间结果传递))
    • [2.2 std::packaged_task 的使用(封装可调用对象为异步任务)](#2.2 std::packaged_task 的使用(封装可调用对象为异步任务))
    • [2.3 异步函数(std::async)的使用(自动管理线程与任务)](#2.3 异步函数(std::async)的使用(自动管理线程与任务))
  • 三、异步编程的实战技巧
    • [3.1 future 的状态管理(valid、wait、get)](#3.1 future 的状态管理(valid、wait、get))
    • [3.2 多任务并发与结果聚合(std::future_vector)](#3.2 多任务并发与结果聚合(std::future_vector))
    • [3.3 异步编程中的异常处理(异常传递与捕获)](#3.3 异步编程中的异常处理(异常传递与捕获))
  • 四、实战项目:异步数据处理系统
    • [4.1 项目需求(异步处理数据计算、获取结果并汇总)](#4.1 项目需求(异步处理数据计算、获取结果并汇总))
    • [4.2 std::async 与 std::future 实现核心逻辑](#4.2 std::async 与 std::future 实现核心逻辑)
    • [4.3 系统响应速度测试与异常场景处理](#4.3 系统响应速度测试与异常场景处理)

一、原子操作的概念与实战

1.1 原子类型(std::atomic)的定义与常用操作(load、store、exchange)

在 C++11 中,std::atomic是一个模板类,用于封装数据类型并为其提供原子操作 ,这些操作在多线程环境中是安全的,不会被其他线程中断。通过使用std::atomic,开发者可以确保数据在多线程环境中的一致性和正确性。例如,定义一个原子整数类型:

cpp 复制代码
#include <atomic>
std::atomic<int> atomicVar(0);

这里atomicVar就是一个原子类型的变量,初始值为 0。

load操作是用来读取原子对象的值,示例代码如下:

cpp 复制代码
#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> x(10);
    int value = x.load();
    std::cout << "Loaded value: " << value << std::endl;
    return 0;
}

上述代码中,x.load()读取了原子变量x的值,并赋值给value。

store操作用于将给定的值存储到原子对象中,代码示例如下:

cpp 复制代码
#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> x(0);
    x.store(20);
    int value = x.load();
    std::cout << "Stored and loaded value: " << value << std::endl;
    return 0;
}

这段代码先创建了一个初始值为 0 的原子变量x,然后使用store将其值设置为 20,最后读取验证。

exchange操作会设置新值并返回旧值,示例如下:

cpp 复制代码
#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> x(100);
    int oldValue = x.exchange(30);
    std::cout << "Old value: " << oldValue << ", New value: " << x.load() << std::endl;
    return 0;
}

上述代码中,x.exchange(30)将x的值设置为 30,并返回原来的值 100。

1.2 原子操作的内存序(memory order):relaxed、acquire - release、seq_cst

在原子操作中,内存序(memory order)用于控制不同线程间内存访问的顺序和可见性。C++11 提供了几种不同的内存序选项,常见的有relaxed、acquire - release和seq_cst。

relaxed(宽松内存序):这是最弱的内存序,它只保证原子操作本身的原子性,不提供任何同步和顺序保证。例如,多个线程对同一个原子变量进行relaxed的store操作,这些操作的顺序在不同线程中可能是不一致的。适用于对顺序和可见性要求不高,仅需保证操作原子性的场景,比如简单的计数器自增。

acquire - release(获取 - 释放内存序):acquire(获取)操作会阻止其之后的内存访问被重排到该操作之前;release(释放)操作会阻止其之前的内存访问被重排到该操作之后。当一个线程对某个原子变量进行release操作,另一个线程对同一个原子变量进行acquire操作时,就建立了一种跨线程的内存同步关系 ,保证release之前的内存修改对acquire之后的内存访问可见。常用于生产者 - 消费者模型等需要一定同步关系的场景。例如:

cpp 复制代码
#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> data(0);
std::atomic<bool> ready(false);

void producer() {
    data.store(42, std::memory_order_release);
    ready.store(true, std::memory_order_release);
}

void consumer() {
    while (!ready.load(std::memory_order_acquire));
    std::cout << "Data: " << data.load(std::memory_order_acquire) << std::endl;
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

在上述代码中,生产者线程先存储数据,再设置ready标志为true,都使用release内存序;消费者线程在ready为true之前一直等待,然后读取数据,都使用acquire内存序。这样能保证消费者读取到的数据是生产者存储的最新数据。

seq_cst(顺序一致性内存序):这是最强的内存序,它保证所有线程对原子变量的操作顺序一致,并且符合程序顺序。它相当于在每个原子操作上都同时具有acquire和release语义,同时还建立了一个全局的顺序。使用seq_cst会带来较高的性能开销,适用于对顺序和可见性要求极高的场景,比如实现无锁数据结构时确保数据一致性。例如:

cpp 复制代码
#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_seq_cst);
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter.load(std::memory_order_seq_cst) << std::endl;
    return 0;
}

在这个示例中,fetch_add和load操作都使用seq_cst内存序,确保了两个线程对counter的操作顺序是一致的,最终得到正确的结果。

1.3 原子操作与互斥锁的性能对比

原子操作和互斥锁都是用于多线程编程中的同步机制,但它们在性能上存在一些差异。

从时间性能来看,原子操作通常比互斥锁更快。原子操作是由硬件指令支持的,在执行时不需要像互斥锁那样进行上下文切换和调度等开销较大的操作。例如,对于简单的计数器自增操作,如果使用互斥锁,每次自增都需要加锁和解锁,而使用原子操作可以直接进行自增,没有额外的锁管理开销。通过以下性能测试代码可以直观感受两者的差异:

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>

std::mutex mtx;
std::atomic<int> atomicCounter(0);
int mutexCounter = 0;

void atomicIncrement() {
    for (int i = 0; i < 1000000; ++i) {
        ++atomicCounter;
    }
}

void mutexIncrement() {
    for (int i = 0; i < 1000000; ++i) {
        std::lock_guard<std::mutex> guard(mtx);
        ++mutexCounter;
    }
}

int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    std::thread t1(atomicIncrement);
    std::thread t2(atomicIncrement);
    t1.join();
    t2.join();
    auto end1 = std::chrono::high_resolution_clock::now();
    auto duration1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();

    auto start2 = std::chrono::high_resolution_clock::now();
    std::thread t3(mutexIncrement);
    std::thread t4(mutexIncrement);
    t3.join();
    t4.join();
    auto end2 = std::chrono::high_resolution_clock::now();
    auto duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();

    std::cout << "Atomic operation time: " << duration1 << " ms" << std::endl;
    std::cout << "Mutex operation time: " << duration2 << " ms" << std::endl;

    return 0;
}

上述代码分别测试了使用原子操作和互斥锁实现的计数器自增操作的执行时间,多次运行可以发现原子操作的执行时间通常会比互斥锁短。

从资源占用角度,互斥锁需要维护锁的状态、等待队列等数据结构,会占用一定的内存空间 ;而原子操作相对来说资源占用较少,它直接基于硬件指令,不需要额外的复杂数据结构来管理同步状态。

综上所述,对于简单的同步操作,如单一变量的增减、状态标志的设置等,原子操作在性能和资源占用上具有优势;而对于需要保护复杂代码段、涉及多个变量之间复杂依赖关系的情况,互斥锁提供了更强大的同步控制能力,但会带来较高的性能开销。在实际编程中,需要根据具体的应用场景和需求来选择合适的同步机制。

二、异步编程模型(C++11)

2.1 std::future 与 std::promise 的使用(线程间结果传递)

在 C++11 的异步编程模型中,std::future和std::promise是两个非常重要的工具,用于在不同线程之间传递数据和同步操作。

std::promise可以看作是一个 "承诺",它用于设置一个值或异常,这个值或异常可以在另一个线程中被获取。std::future则像是一个 "未来的结果",它提供了一种异步获取std::promise设置的值或异常的机制。通过std::promise和std::future,我们可以实现线程间的数据传递和同步,使得主线程不必等待异步任务完成就可以继续执行其他操作。

下面通过一个简单的示例代码来演示它们的用法:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

// 线程函数,模拟一些耗时操作并设置结果
void task(std::promise<int>& prom) {
    std::this_thread::sleep_for(std::chrono::seconds(2));// 模拟耗时操作
    int result = 42;
    prom.set_value(result);
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread t(task, std::ref(prom));// 启动新线程并传递promise

    // 主线程可以继续执行其他任务
    std::cout << "Main thread is doing other things..." << std::endl;

    int result = fut.get();// 等待并获取线程函数的结果
    std::cout << "The result from the thread is: " << result << std::endl;

    t.join();// 等待线程结束
    return 0;
}

在上述代码中:

  • 首先创建了一个std::promise<int>对象prom和与之关联的std::future<int>对象fut。
  • 然后启动一个新线程执行task函数,并将prom以引用的方式传递给它。
  • 在task函数中,模拟了一个耗时操作(睡眠 2 秒),然后通过prom.set_value(result)设置了结果。
  • 主线程在调用fut.get()时会阻塞,直到prom设置了值,此时fut.get()会返回设置的值,从而实现了线程间的结果传递。

2.2 std::packaged_task 的使用(封装可调用对象为异步任务)

std::packaged_task是 C++11 中用于封装可调用对象(如函数、lambda 表达式、函数对象等)的类模板,使其可以异步执行,并通过std::future获取执行结果。它提供了一种方便的方式来管理异步任务,将任务的执行和结果获取分离开来。

std::packaged_task的使用步骤如下:

  1. 创建一个std::packaged_task对象,传入可调用对象作为参数。
  2. 通过std::packaged_task对象的get_future方法获取一个std::future对象,用于获取任务的执行结果。
  3. 将std::packaged_task对象传递给线程执行,或者直接调用它。
  4. 在需要的时候,通过std::future对象获取任务的执行结果。

下面是一个使用std::packaged_task的示例代码:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

// 可调用函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 创建packaged_task对象,封装add函数
    std::packaged_task<int(int, int)> task(add);
    // 获取与之关联的future对象
    std::future<int> result_future = task.get_future();

    // 使用线程执行任务
    std::thread t(std::move(task), 3, 5);

    // 主线程可以继续执行其他任务
    std::cout << "Main thread is doing other things..." << std::endl;

    // 获取任务执行结果
    int result = result_future.get();
    std::cout << "The result of the task is: " << result << std::endl;

    t.join();// 等待线程结束
    return 0;
}

在这个示例中:

  • 首先定义了一个add函数,用于计算两个整数的和。
  • 然后创建了一个std::packaged_task<int(int, int)>对象task,并将add函数封装进去。
  • 通过task.get_future()获取一个std::future<int>对象result_future,用于获取任务的执行结果。
  • 使用std::thread启动一个新线程来执行task,并传入参数 3 和 5。
  • 主线程继续执行其他任务,最后通过result_future.get()获取任务的执行结果并输出。

2.3 异步函数(std::async)的使用(自动管理线程与任务)

std::async是 C++11 中提供的一个更高级的异步编程工具,它可以自动管理线程和任务的执行。通过std::async,可以方便地创建一个异步任务,并在需要的时候获取其执行结果,而无需手动创建和管理线程。

std::async的基本用法如下:

cpp 复制代码
std::future<ReturnType> result = std::async(std::launch::async | std::launch::deferred, Function, Args...);
  • std::launch::async表示立即启动一个新线程来执行任务。
  • std::launch::deferred表示延迟执行任务,直到调用std::future的get或wait方法时才执行。
  • Function是要执行的函数或可调用对象。
  • Args...是传递给函数的参数。

std::async返回一个std::future对象,通过这个对象可以获取异步任务的执行结果。

下面是一个使用std::async的示例代码:

cpp 复制代码
#include <iostream>
#include <future>

// 异步任务函数
int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int main() {
    // 启动异步任务
    std::future<int> result_future = std::async(std::launch::async, factorial, 5);

    // 主线程可以继续执行其他任务
    std::cout << "Main thread is doing other things..." << std::endl;

    // 获取异步任务的结果
    int result = result_future.get();
    std::cout << "The factorial of 5 is: " << result << std::endl;

    return 0;
}

在这个示例中:

  • 使用std::async启动了一个异步任务,执行factorial函数,计算 5 的阶乘。
  • 主线程继续执行其他任务,输出 "Main thread is doing other things..."。
  • 最后通过result_future.get()获取异步任务的执行结果并输出。std::async会自动管理线程的创建和销毁,使得异步编程更加简洁和高效。

三、异步编程的实战技巧

3.1 future 的状态管理(valid、wait、get)

在 C++ 的异步编程中,std::future的状态管理至关重要,它主要通过valid、wait和get这几个函数来实现。

valid函数用于检查std::future对象是否拥有共享状态,即是否有与之关联的异步任务。如果std::future对象是通过std::async、std::packaged_task或std::promise正确初始化的,那么valid函数将返回true;否则返回false。例如:

cpp 复制代码
#include <iostream>
#include <future>

int main() {
    std::future<int> fut1; // 未初始化的future
    std::cout << "fut1 valid: " << (fut1.valid() ? "true" : "false") << std::endl;

    std::future<int> fut2 = std::async([]() { return 42; });
    std::cout << "fut2 valid: " << (fut2.valid() ? "true" : "false") << std::endl;

    return 0;
}

上述代码中,fut1未初始化,valid返回false;fut2通过std::async初始化,valid返回true。

wait函数用于阻塞当前线程,直到与之关联的异步任务完成 ,即异步任务的共享状态变为就绪。它常用于在不获取异步任务结果的情况下,等待任务执行结束。比如:

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

void asyncTask() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Async task finished" << std::endl;
}

int main() {
    std::future<void> fut = std::async(asyncTask);
    std::cout << "Waiting for async task to finish..." << std::endl;
    fut.wait();
    std::cout << "Async task has finished" << std::endl;

    return 0;
}

在这段代码中,主线程调用fut.wait()后会阻塞,直到asyncTask函数执行完毕,输出 "Async task finished",然后主线程继续执行后续代码。

get函数同样会阻塞当前线程,直到异步任务完成,并返回异步任务的结果。与wait函数不同的是,get函数会获取异步任务的返回值(如果有),并且在获取结果后,std::future对象的共享状态会被释放,此时再调用valid函数将返回false。例如:

cpp 复制代码
#include <iostream>
#include <future>

int asyncCalculation() {
    return 2 + 3;
}

int main() {
    std::future<int> fut = std::async(asyncCalculation);
    int result = fut.get();
    std::cout << "The result of async calculation is: " << result << std::endl;
    std::cout << "fut valid after get: " << (fut.valid() ? "true" : "false") << std::endl;

    return 0;
}

上述代码中,fut.get()获取异步任务的结果并赋值给result,之后fut.valid()返回false,表明共享状态已被释放。

通过合理使用valid、wait和get函数,开发者可以精确地管理std::future的状态,实现高效的异步编程。

3.2 多任务并发与结果聚合(std::future_vector)

在实际的异步编程中,常常需要同时执行多个异步任务,并将这些任务的结果进行聚合处理。虽然 C++ 标准库中并没有直接提供std::future_vector,但我们可以通过std::vector<std::future>来实现类似的功能。

std::vector<std::future>的原理是将多个std::future对象存储在一个向量中,每个std::future对应一个异步任务。这样可以方便地管理和操作多个异步任务,通过遍历向量,可以分别等待每个任务完成并获取其结果 ,从而实现多任务并发执行和结果聚合。

下面是一个使用std::vector<std::future>实现多任务并发处理的示例代码:

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

// 模拟异步任务函数,返回输入值的平方
int square(int num) {
    return num * num;
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<std::future<int>> futures;

    // 启动多个异步任务
    for (int num : numbers) {
        futures.emplace_back(std::async(square, num));
    }

    std::vector<int> results;
    // 等待所有任务完成并获取结果
    for (auto& fut : futures) {
        results.push_back(fut.get());
    }

    // 计算结果总和
    int sum = std::accumulate(results.begin(), results.end(), 0);

    std::cout << "The sum of squares is: " << sum << std::endl;

    return 0;
}

在上述代码中:

  • 首先定义了一个square函数,用于计算输入值的平方。
  • 然后创建了一个包含多个整数的numbers向量和一个用于存储std::future对象的futures向量。
  • 通过循环遍历numbers向量,为每个数字启动一个异步任务,将square函数应用到每个数字上,并将返回的std::future对象存储到futures向量中。
  • 接着再次循环遍历futures向量,调用每个std::future对象的get方法,等待异步任务完成并获取结果,将结果存储到results向量中。
  • 最后使用std::accumulate函数计算results向量中所有结果的总和并输出。通过这种方式,实现了多任务的并发执行和结果聚合。

3.3 异步编程中的异常处理(异常传递与捕获)

在异步编程中,异常处理是一个重要的环节。由于异步任务在独立的线程中执行,异常的传递和捕获与普通同步编程有所不同。

异步任务中异常产生的原因通常与任务本身的逻辑错误有关,例如除零操作、内存访问越界、调用外部接口失败等。当异步任务中抛出异常时,如果不进行正确处理,可能会导致程序崩溃或出现未定义行为。

在 C++ 中,std::future提供了一种机制来处理异步任务中的异常。当异步任务抛出异常时,异常会被存储在std::future的共享状态中。通过调用std::future的get方法获取结果时,如果异步任务抛出了异常,get方法会重新抛出这个异常,从而可以在调用get的地方进行捕获和处理。

下面是一个演示异步编程中异常传递与捕获的示例代码:

cpp 复制代码
#include <iostream>
#include <future>

// 模拟异步任务函数,可能抛出异常
int asyncTask() {
    if (rand() % 2 == 0) {
        throw std::runtime_error("Async task error");
    }
    return 42;
}

int main() {
    std::future<int> fut = std::async(asyncTask);

    try {
        int result = fut.get();
        std::cout << "The result of async task is: " << result << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

在这个示例中:

  • asyncTask函数模拟了一个可能抛出异常的异步任务,通过rand() % 2 == 0来随机决定是否抛出std::runtime_error异常。
  • 在main函数中,启动异步任务并获取对应的std::future对象。
  • 在try块中调用fut.get()获取异步任务的结果,如果asyncTask函数抛出异常,fut.get()会重新抛出该异常,然后在catch块中捕获并处理这个异常,输出异常信息。这样就实现了异步任务中异常的传递与捕获处理,确保程序在遇到异常时能够进行适当的处理,而不是直接崩溃。

四、实战项目:异步数据处理系统

4.1 项目需求(异步处理数据计算、获取结果并汇总)

本项目旨在构建一个异步数据处理系统,以应对大规模数据处理场景下对系统性能和响应速度的高要求。具体需求如下:

  • 异步处理数据计算:系统需要接收大量的数据,这些数据可能来自文件读取、网络传输等不同数据源。针对这些数据,需要进行复杂的计算操作,如数据清洗、统计分析、复杂算法运算等。为了提高处理效率,避免阻塞主线程,这些计算操作应异步执行,使系统在处理数据的同时能够响应其他任务。
  • 获取结果并汇总:每个异步计算任务完成后,需要及时获取其计算结果。并且,系统要能够将这些分散的结果进行汇总,形成一个完整的处理结果集。汇总结果的格式和内容应根据具体业务需求进行定制,可能包括统计报表、分析结论等。
  • 性能与稳定性:系统应具备良好的性能表现,能够在短时间内处理大量数据,确保处理速度满足业务要求。同时,要保证系统在高负载和复杂环境下的稳定性,能够正确处理各种异常情况,如任务执行失败、数据格式错误等,避免系统崩溃或出现数据丢失、错误等问题。

4.2 std::async 与 std::future 实现核心逻辑

基于上述项目需求,下面通过代码展示如何利用std::async与std::future实现系统的核心逻辑。假设我们有一组数据,需要对每个数据进行平方计算,并将结果汇总:

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

// 模拟数据计算函数,返回输入值的平方
int square(int num) {
    return num * num;
}

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    std::vector<std::future<int>> futures;

    // 启动多个异步任务,对每个数据进行平方计算
    for (int num : data) {
        futures.emplace_back(std::async(square, num));
    }

    std::vector<int> results;
    // 等待所有任务完成并获取结果
    for (auto& fut : futures) {
        results.push_back(fut.get());
    }

    // 汇总结果,这里简单计算结果的总和
    int sum = std::accumulate(results.begin(), results.end(), 0);

    std::cout << "The sum of squared results is: " << sum << std::endl;

    return 0;
}

在上述代码中:

  • 首先定义了一个square函数,用于计算输入值的平方,模拟实际的数据计算任务。
  • 创建了一个包含多个整数的data向量,代表需要处理的数据集合。
  • 通过循环遍历data向量,使用std::async启动异步任务,将square函数应用到每个数据上,并将返回的std::future对象存储到futures向量中 ,实现了异步处理数据计算。
  • 再次循环遍历futures向量,调用每个std::future对象的get方法,等待异步任务完成并获取结果,将结果存储到results向量中 ,完成了结果的获取。
  • 最后使用std::accumulate函数计算results向量中所有结果的总和,实现了结果的汇总。

4.3 系统响应速度测试与异常场景处理

为了评估系统的性能,我们进行系统响应速度测试。可以使用std::chrono库来测量从启动异步任务到获取汇总结果的总时间。测试代码如下:

cpp 复制代码
#include <iostream>
#include <future>
#include <vector>
#include <numeric>
#include <chrono>

// 模拟数据计算函数,返回输入值的平方
int square(int num) {
    return num * num;
}

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    std::vector<std::future<int>> futures;

    auto start = std::chrono::high_resolution_clock::now();

    // 启动多个异步任务,对每个数据进行平方计算
    for (int num : data) {
        futures.emplace_back(std::async(square, num));
    }

    std::vector<int> results;
    // 等待所有任务完成并获取结果
    for (auto& fut : futures) {
        results.push_back(fut.get());
    }

    // 汇总结果,这里简单计算结果的总和
    int sum = std::accumulate(results.begin(), results.end(), 0);

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

    std::cout << "The sum of squared results is: " << sum << std::endl;
    std::cout << "Total processing time: " << duration << " ms" << std::endl;

    return 0;
}

多次运行上述测试代码,可以得到不同的处理时间,通过分析这些时间数据,可以评估系统在当前数据规模下的响应速度。

在异常场景处理方面,考虑以下几种常见情况:

  • 任务执行失败抛出异常:如果square函数在执行过程中抛出异常,例如:
cpp 复制代码
int square(int num) {
    if (num == 0) {
        throw std::runtime_error("Invalid input: zero");
    }
    return num * num;
}

此时,在获取结果时可以通过try - catch块捕获异常并进行处理:

cpp 复制代码
std::vector<int> results;
try {
    // 等待所有任务完成并获取结果
    for (auto& fut : futures) {
        results.push_back(fut.get());
    }
} catch (const std::exception& e) {
    std::cerr << "Caught exception: " << e.what() << std::endl;
}

这样可以避免因为某个任务抛出异常而导致整个系统崩溃。

  • 数据格式错误:在实际应用中,可能会遇到输入数据格式错误的情况。可以在数据读取阶段进行数据格式校验,例如:
cpp 复制代码
std::vector<int> data;
// 假设从文件读取数据
std::ifstream file("data.txt");
int value;
while (file >> value) {
    if (value < 0) {
        std::cerr << "Invalid data: negative value" << std::endl;
        continue;
    }
    data.push_back(value);
}

通过这种方式,在数据进入计算环节之前就对错误数据进行过滤和处理,保证计算任务的正确性和稳定性。通过合理的响应速度测试和全面的异常场景处理,可以使异步数据处理系统更加健壮和高效。

相关推荐
Mr_WangAndy3 小时前
C++设计模式_结构型模式_适配器模式Adapter
c++·设计模式·适配器模式·c++设计模式
bkspiderx3 小时前
C++设计模式之结构型模式:代理模式(Proxy)
c++·设计模式·代理模式
bkspiderx5 小时前
C++设计模式之行为型模式:解释器模式(Interpreter)
c++·设计模式·解释器模式
倔强菜鸟5 小时前
2025.8.10-学习C++(一)
开发语言·c++·学习
ZXF_H6 小时前
C/C++预定义宏与调试日志输出模板
开发语言·c++·日志·调试·预定义宏
2401_841495646 小时前
【数据结构】顺序表的基本操作
数据结构·c++·算法·顺序表·线性表·线性结构·顺序表的基本操作
小糖学代码7 小时前
STL的list模拟实现(带移动构造和emplace版本)
c语言·数据结构·c++·windows·list
王嘉俊9257 小时前
Qt 入门:构建跨平台 GUI 应用的强大框架
c语言·开发语言·c++·qt·入门·cpp
老歌老听老掉牙7 小时前
OpenCASCADE 点云拟合曲线与曲面:从零实现到工业级应用
c++·点云·opencascade