当前位置: 首页 > article >正文

windows C++-有效使用PPL(三)

了解取消和异常处理如何影响对象销毁

在并行工作树中,取消的任务会阻止子任务运行。 如果一个子任务执行的操作对于应用程序很重要(如释放资源),则这可能会导致问题。 此外,任务取消可能导致异常通过对象析构函数进行传播,并在应用程序中导致不明确的行为。

在下面的示例中,Resource 类描述资源,Container 类描述保存资源的容器。 在其析构函数中,Container 类在它两个 Resource 成员上并行调用 cleanup 方法,然后在它第三个 Resource 成员上调用 cleanup 方法。

// parallel-resource-destruction.h
#pragma once
#include <ppl.h>
#include <sstream>
#include <iostream>

// Represents a resource.
class Resource
{
public:
   Resource(const std::wstring& name)
      : _name(name)
   {
   }

   // Frees the resource.
   void cleanup()
   {
      // Print a message as a placeholder.
      std::wstringstream ss;
      ss << _name << L": Freeing..." << std::endl;
      std::wcout << ss.str();
   }
private:
   // The name of the resource.
   std::wstring _name;
};

// Represents a container that holds resources.
class Container
{
public:
   Container(const std::wstring& name)
      : _name(name)
      , _resource1(L"Resource 1")
      , _resource2(L"Resource 2")
      , _resource3(L"Resource 3")
   {
   }

   ~Container()
   {
      std::wstringstream ss;
      ss << _name << L": Freeing resources..." << std::endl;
      std::wcout << ss.str();

      // For illustration, assume that cleanup for _resource1
      // and _resource2 can happen concurrently, and that 
      // _resource3 must be freed after _resource1 and _resource2.

      concurrency::parallel_invoke(
         [this]() { _resource1.cleanup(); },
         [this]() { _resource2.cleanup(); }
      );

      _resource3.cleanup();
   }

private:
   // The name of the container.
   std::wstring _name;

   // Resources.
   Resource _resource1;
   Resource _resource2;
   Resource _resource3;
};

尽管这种模式在其自身上没有任何问题,但建议使用下面并行运行两个任务的代码。 第一个任务创建 Container 对象,第二个任务取消整个任务。 举例来说,该示例使用两个 concurrency::event 对象来确保在创建 Container 对象后发生取消,并在取消操作发生后销毁 Container 对象。

// parallel-resource-destruction.cpp
// compile with: /EHsc
#include "parallel-resource-destruction.h"

using namespace concurrency;
using namespace std;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{  
   // Create a task_group that will run two tasks.
   task_group tasks;

   // Used to synchronize the tasks.
   event e1, e2;

   // Run two tasks. The first task creates a Container object. The second task
   // cancels the overall task group. To illustrate the scenario where a child 
   // task is not run because its parent task is cancelled, the event objects 
   // ensure that the Container object is created before the overall task is 
   // cancelled and that the Container object is destroyed after the overall 
   // task is cancelled.
   
   tasks.run([&tasks,&e1,&e2] {
      // Create a Container object.
      Container c(L"Container 1");
      
      // Allow the second task to continue.
      e2.set();

      // Wait for the task to be cancelled.
      e1.wait();
   });

   tasks.run([&tasks,&e1,&e2] {
      // Wait for the first task to create the Container object.
      e2.wait();

      // Cancel the overall task.
      tasks.cancel();      

      // Allow the first task to continue.
      e1.set();
   });

   // Wait for the tasks to complete.
   tasks.wait();

   wcout << L"Exiting program..." << endl;
}

该示例产生下面的输出:

Container 1: Freeing resources...Exiting program...

此代码示例包含的以下问题可能导致其与预期行为不同:

  • 父任务的取消会导致子任务(对 concurrency::parallel_invoke 的调用)也被取消。 因此,不会释放这两个资源;
  • 父任务的取消将导致子任务引发内部异常。 由于 Container 析构函数不会处理此异常,因此异常会向上传播并且不会释放第三个资源;
  • 子任务引发的异常通过 Container 析构函数进行传播。 从析构函数引发会将应用程序置于未定义的状态;

我们建议不在任务中执行关键操作(如释放资源),除非可以保证这些任务不会被取消。 我们还建议不使用可在类型的析构函数中引发的运行时功能。

不要在并行循环中反复阻止

并行循环(例如通过停滞操作进行控制的 concurrency::parallel_for 或 concurrency::parallel_for_each)可能导致运行时在很短的时间内创建许多线程。

当任务完成,或以协作方式停滞或让出时,并发运行时将执行其他工作。 当一个并行循环迭代停滞时,运行时可能会开始另一个迭代。 当不存在可用的空闲线程时,运行时将创建一个新线程。

当并行循环体偶尔停滞时,此机制可帮助最大化整体任务吞吐量。 但当多个迭代停滞时,运行时可能会创建多个线程来运行其他工作。 这可能导致内存不足的情况或较差的硬件资源使用情况。

请考虑以下示例,该示例在 parallel_for 循环的每次迭代中调用 concurrency::send 函数。 由于 send 以协作方式停滞,所以每次调用 send 时运行时都会创建一个新线程来运行其他工作。

// repeated-blocking.cpp
// compile with: /EHsc
#include <ppl.h>
#include <agents.h>

using namespace concurrency;

static_assert(false, "This example illustrates a non-recommended practice.");

int main()
{
   // Create a message buffer.
   overwrite_buffer<int> buffer;
  
   // Repeatedly send data to the buffer in a parallel loop.
   parallel_for(0, 1000, [&buffer](int i) {
      
      // The send function blocks cooperatively. 
      // We discourage the use of repeated blocking in a parallel
      // loop because it can cause the runtime to create 
      // a large number of threads over a short period of time.
      send(buffer, i);
   });
}

我们建议你重构代码来避免这种模式。 在此示例中,可通过在串行 for 循环中调用 send 来避免其他线程的创建。


http://www.kler.cn/news/355603.html

相关文章:

  • 力扣 简单 141.环形链表
  • Miniconda管理虚拟环境【Python环境配置】
  • 【JS、数组】flat的基本用法
  • 开源vGPU方案 HAMi实现细粒度GPU切分——筑梦之路
  • 观测云 AI 助手上线:智能运维,从此触手可及!
  • 使用软件模拟按键显示屏,上下左右确认取消按键,来修改IP端口号等参数。
  • Hi3061M——VL53L0X激光测距(IIC)(同样适用于其他MCU)2
  • rk3588 opencv 的使用
  • Android 编译时出现Android resource linking failed.without required default value.
  • perl读取目录,写入文件
  • 高校企业数据可视化平台功能介绍/特色功能
  • 骑砍霸主MOD天芒传奇Ⅱ·前传-序章
  • 压缩感知解谱
  • EI会议将截稿|第三届环境工程与与可持续能源国际会议(EESE 2024)
  • Git常用操作
  • 国家计算机二级MSOffice计算机选择题题库汇总精选
  • VS code部署Vue项目Demo
  • Apache POI
  • C#从零开始学习(基本语法概念)(2)
  • 腐蚀膨胀预处理