闭关之 C++ 并发编程笔记(五):并行算法函数与测试

第10章 并行算法函数

10.1 并行化的标准库算法函数

  • C++17 向标准库加入了并行算法函数。它们是新引入的多个函数重载
    • 如std::find()
    • std::transform()
    • std::reduce()
    • 其操作目标都是容器区间。相比各自对应的“普通的”单线程版本,这些并行版本具有相同的函数签名,只是新增了一个参数,用于设定执行策略,该参数排在参数列表最前面。
      std::vector<int> my_data;
      std::sort(std::execution::par,my_data.begin(),my_data.end());
      
    • 执行策略std::execution::par
      • 准许该调用采用多线程,按并行算法的形式执行
  • 并行执行方式会改变算法函数对复杂度的要求,比普通串行算法函数对复杂度的要求略为宽松

10.2 执行策略

  • C++17标准制定了3种执行策略
    • std::execution::sequenced_polic
    • std::execution::parallel_policy
    • std::execution::parallel_unsequenced_policy
  • 它们是3个类,由头文件 <execution> 定义
  • 该头文件还定义了3个对应的策略对象,作为参数向算法函数传递
    • std::execution::seq
    • std::execution::pa
    • std::execution::par_unseq

10.2.1 因指定执行策略而普遍产生的作用

  • 向标准库的算法函数传入执行策略的参数,则函数行为受控于该策略。
  • 其行为在以下方面受到影响
    • 算法函数的复杂度
    • 抛出异常时的行为。
    • 算法函数的步骤会在何时、从何处、以何种方式执行。
    • 对算法函数复杂度所产生的作用
  • 异常行为
    • 如果按某种执行策略调用算法函数,而期间有异常抛出,则后果取决于所选用的执行策略。
    • 如果有异常未被捕获,由 C++ 标准给出的 3 种执行策略就会调用std::terminate()。
    • 只要按标准的执行策略调用标准库的算法函数,抛出的异常其实只有一种
      • std::bad_alloc异常
      • 当程序库无法为内部操作分配足够内存资源时即抛出该异常
  • 算法中间步骤的执行起点和执行时机
    • 执行策略指定了算法函数的中间步骤的执行主体
    • 可能是平常的CPU线程、向量流(vector stream)、GPU线程或任何其他运算单元
  • 还指定了算法的中间步骤存在的内存次序约束
    • 这些步骤是否服从某种特定次序
    • 独立的步骤之间是否可以互相交错执行
    • 或是否可以彼此并行执行,等等

10.2.2 std::execution::sequenced_policy

  • 顺序策略(sequenced policy)与并行无关
    • 它令算法函数在发起调用的线程上执行全部操作,因而不会发生并行
  • 几乎没有施加内存次序限制
    • 它们之间可以自由选择同步机制,也会因同一线程上的操作而发生变化,但不得假设存在完全确定的操作次序

10.2.3 std::execution::parallel_policy

  • 并行策略(parallel policy)给出了多个线程并行的基本
  • 并行策略对这些目标的内存次序施加了更多限制
    • 若它们涉及并行操作,就绝不能引发数据竞争,也不得假设其他任何操作会由同一个线程执行,还不得假设其他任何操作一定会由别的线程执行
  • 绝大多数情况下,我们都可以令其采用并行策略
    • 只有在下述情况下才会引发问题
      • 某些元素的操作要求服从特定的次序,或共享数据的访问之间没有同步

10.2.4 std::execution::parallel_unsequenced_policy

  • 针对算法函数用到的各种迭代器、值和可调用对象,非顺序并行策略(parallel unsequenced policy)就其内存次序施加了最严格的限制,以便标准库最大程度发挥算法并行化的潜力
  • 如果令算法函数采用非顺序并行策略,它就会在多个线程上按乱序执行算法步骤,线程之间的操作将不服从代码流程的先后顺序

10.3 C++ 标准库的并行算法函数

  • 算法函数由头文件 <algorithm> 和 ` 给出
    • 其中大多数具有可以指定执行策略的重载版
  • C++ 标准库的迭代器类别
    • 输入迭代器(input iterator)
      • 属于单通迭代器,用途是获取值,它常常用于控制台或网络的输入,我们也用它从生成序列中取得数据
    • 输出迭代器(output iterator)
      • 属于单通迭代器,用途是写出值。它常常用于文件输出,向容器添加新值。输出迭代器的步进会令其副本失效
    • 前向迭代器(forward iterator)
      • 属于多通(multi-pass)迭代器,用途是单向迭代持久化数据。虽然我们无法使前向迭代器逆转,返回过往的位置,但我们可以保存其居于某个位置时的副本,以提取早前访问过的元素
    • 双向迭代器(bidirectional iterator)
      • 双向迭代器属于多通迭代器,但它可以折返,可以访问前面的元素
    • 随机访问迭代器(random access iterator)
      • 属于多通迭代器,前向、后向移动皆可,但其移动距离不再限于单个元素,我们可以通过它的数组索引运算符,按偏移量直接访问目标元素

第11章 多线程应用的测试和除错

11.1 与并发相关的错误类型

  • 并发关联的错误分为两大类型。
    • 多余的阻塞
    • 条件竞争

11.1.1 多余的阻塞

  • 若线程等待某项条件成立或某一状态出现,而无法继续处理任务,即称它被阻塞
    • 等待目标可能是互斥、条件变量或future,也可能是I/O操作

11.1.2 条件竞争

  • 条件竞争经常造成的问题类型如下
    • 数据竞争
      • 对共享内存区域的并发访问未采取同步措施,结果导致未定义行为
    • 受到破坏的不变量
      • 悬空指针
        • 当前线程正在通过指针访问目标数据,而其他线程却同时删除指针
      • 随机的内存数据损坏
        • 数据正更新到一半,而其他线程却同时读取,造成数据不一致
      • 重复释放内存
        • 两个线程同时从队列弹出相同的值,它们都删除某份关联的数据
    • 生存期问题
      • 线程的生存期超出了它所访问的数据的生存期
      • 数据被删除或以其他方式销毁后,线程仍试图访问它们,而相应的存储空间有可能已被另一个对象重用

11.2 定位并发相关的错误的技法

11.2.1 审查代码并定位潜在错误

  • 多线程代码审查中需要考虑的问题
    • 如果要进行并发访问,哪些数据需要保护
    • 如何确保数据受到保护
    • 若当前线程正在操作受保护的数据,那么其他线程可能同时在执行什么代码
    • 当前线程持有哪些互斥
    • 其他线程可能持有哪些互斥
    • 当前线程和其他线程上的操作需要服从什么次序?该次序限制如何强制实施
    • 当前线程所读取的数据是否仍旧合法、有效?该数据是否有可能已被其他线程改动过
    • 如果假定其他线程有可能以并发方式改动数据,那么该改动的发生条件和影响是什么?我们如何能保证改动不会发生

11.2.2 通过测试定位与并发相关的错误

  • 将应用软件调整为单线程模式,而错误依旧,即说明错误成因不是并发功能
  • 测试环境考虑因素
    • 每项测试中多线程的数目是多少
    • 硬件系统所具有的处理器内核是否足够,能否让每个线程独具一个内核
    • 应该在哪些处理器架构上运行测试
    • 我们能否确保系统进行合理调度,使测试中的操作真正实现“同时”和“并发”

11.2.3 设计可测试的代码

  • 通常只要做到以下几点,代码就相对容易测试
    • 每个函数和类的职责清楚明确
    • 函数短小精悍,功能切中要害
    • 接受测试的目标代码处于测试环境中,而实施测试的代码和用例可以完全掌控该环境。
    • 执行特定操作的相关代码应该汇聚在一起,以方便测试,不得散布于整个系统中
    • 着手编写代码前,先想清楚如何对其进行测试

11.2.4 多线程测试技术

  • 强力测试
    • 让代码承受压力运行,看它是否崩溃
    • 缺点
      • 可能导致错误置信(特定环境才能复现)
  • 组合模拟测试
    • 用特定软件模拟真实的运行时环境,并在其中运行受测代码
    • 两种测试方法
      • 在普通环境下多次运行测试,但可能错失某些错误
      • 特定的模拟环境中多次运行测试,而这更像是追查已经存在的错误
  • 采用特殊的程序库检测错误

11.2.5 以特定结构组织多线程的测试代码

  • 这种测试的根本问题是,我们需要编排一组线程,为其中每一个线程分别选定目标代码,并令它们同时执行

11.2.6测试多线程代码的性能

  • 若要测试多线程代码性能,最好在尽量多的、不同硬件配置的系统上进行,这样我们才能清楚分析软件的可伸缩性
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值