《C++并发编程实战》 第4章 并发操作的同步

4.2 使用future等待一次性事件发生

  • 两种future:独占future(unique future,即std::future<>)和共享future(shared future,即std::shared_future<>
cpp 复制代码
#include <future>
#include <iostream>
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
    std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    std::cout<<"The answer is "<<the_answer.get()<<std::endl;
}
  • 不过,我们还能够给std::async()补充一个参数,以指定采用哪种运行方式。参数的类型是std::launch,其值可以是std::launch::deferred或std::launch::async[8]。前者指定在当前线程上延后调用任务函数,等到在future上调用了wait()或get(),任务函数才会执行;后者指定必须另外开启专属的线程,在其上运行任务函数。该参数的值还可以是std::launch::deferred | std::launch:: async,表示由std::async()的实现自行选择运行方式。最后这项是参数的默认值。若延后调用任务函数,则任务函数有可能永远不会运行

4.2.2 关联future实例和任务

三种方式其实代表了 C++ 并发编程中,从 "全自动" 到 "全手动" 的三种不同粒度的控制权。它们的核心目的都是为了获取一个 std::future(未来的结果),但**"谁来执行"以及"如何传递结果"**的方式不同。




4.2.4 将异常保存到future中

  • 包装的任务函数在执行时抛出异常,则会代替本应求得的结果,被保存到future内并使其准备就绪。只要调用get(),该异常就会被再次抛出。
cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <exception>

void bad_chef(std::promise<int> prom) {
    // 厨师拿到订单(promise)
    std::cout << "厨师: 我拿到订单了,但我不想做饭..." << std::endl;
    
    // 正常流程应该调用 prom.set_value(100);
    // 但是!这里什么都没做,函数结束了。
    // prom 是局部变量,函数结束时它会被【销毁/析构】。
} 

int main() {
    // 1. 创建承诺
    std::promise<int> prom;
    
    // 2. 获取未来的票据
    std::future<int> f = prom.get_future();

    // 3. 开启线程,把 promise 移交给厨师(移动语义)
    std::thread t(bad_chef, std::move(prom));

    // 4. 顾客等待取餐
    try {
        std::cout << "顾客: 我在等饭..." << std::endl;
        int result = f.get(); // 这里会阻塞等待
        std::cout << "顾客: 拿到饭了: " << result << std::endl;
    } 
    catch (const std::future_error& e) {
        // 5. 捕获到了 Broken Promise 异常!
        std::cout << "\n>>> 顾客生气了: 发生错误! <<<" << std::endl;
        std::cout << "    错误代码: " << e.code() << " (broken_promise)" << std::endl;
        std::cout << "    原因: 厨师(Promise)销毁前没有给值。" << std::endl;
    }

    t.join();
    return 0;
}

4.2.5 多个线程一起等待

C++ 并发编程中 "单次消费"与"多次广播"



4.3 限时等待

两种超时(timeout)机制可供选用:一是迟延超时(duration-based timeout)​,线程根据指定的时长而继续等待(如30毫秒)​;二是绝对超时(absolute timeout)​,在某特定时间点(time point)来临之前,线程一直等待。大部分等待函数都具有变体,专门处理这两种机制的超时。处理迟延超时的函数变体以"_for"为后缀,而处理绝对超时的函数变体以"_until"为后缀。

4.3.1 时钟类


4.3.2 时长类


  • 类型转换
    大单位 -> 小单位(自动/隐式): 1 小时 -> 3600 秒。这是绝对安全的,数据不会丢,所以可以自动转。

4.3.3 时间点类



4.4 运用同步操作简化代码

4.4.1 利用future进行函数式编程

list中的splice

它的核心作用是:将元素从一个 list 移动到另一个 list(或同一个 list 的不同位置)。核心优势:零拷贝,极速 (O(1)O(1)O(1))与 std::vector 不同,list 的 splice 操作不会复制或移动数据本身,也不会分配或释放内存。它只是简单地修改了节点之间的指针指向。

std::partition

它的核心作用是:根据你指定的条件(谓词),将容器中的元素"一分为二

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

template <typename T>
std::list<T> parallel_quick_sort(std::list<T> input)
{
    if (input.empty())
    {
        return input;
    }

    std::list<T> result;
    // 取出第一个元素作为基准
    result.splice(result.begin(), input, input.begin());
    T const &pivot = *result.begin();

    // 根据基准划分元素为两半
    auto divide_point = std::partition(input.begin(), input.end(), [&](T const &t)
                                       { return t < pivot; });
    // 将input中小于基准的元素取出放到lower_part中
    sdt::list<T> lower_part;
    lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
    // 开启一个现场基础递归快排小于基准的部分
    std::future<std::list<T>> new_lower(std::async(&*parallel_quick_sort<T>, std::move(lower_part)));
    // 递归快排大于等于基准的部分
    auto new_higher(parallel_quick_sort(std::move(input)));
    // 将排好序的小于基准的部分加入结果中的基准的后面
    result.splice(result.end(), new_higher);
    // 等待小于基准的部分排好序后加入结果中的基准的前面
    result.splice(result.begin(), new_lower.get());
    // 这里的get()会阻塞直到new_lower完成
    return result;
}

四个概念纠清




4.4.2 使用消息传递进行同步

std::experimental

相关推荐
kylezhao201918 分钟前
C# 中的 SOLID 五大设计原则
开发语言·c#
王老师青少年编程24 分钟前
2024年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)
c++·题解·真题·csp·信奥赛·csp-s·提高组
凡人叶枫1 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
CSDN_RTKLIB1 小时前
使用三方库头文件未使用导出符号情景
c++
春日见1 小时前
车辆动力学:前后轮车轴
java·开发语言·驱动开发·docker·计算机外设
锐意无限1 小时前
Swift 扩展归纳--- UIView
开发语言·ios·swift
低代码布道师1 小时前
Next.js 16 全栈实战(一):从零打造“教培管家”系统——环境与脚手架搭建
开发语言·javascript·ecmascript
念何架构之路1 小时前
Go进阶之panic
开发语言·后端·golang
亓才孓2 小时前
[Properties]写配置文件前,必须初始化Properties(引用变量没执行有效对象,调用方法会报空指针错误)
开发语言·python
傻乐u兔2 小时前
C语言进阶————指针3
c语言·开发语言