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

windows C++-并发中的最佳做法(一)

本文档介绍适用于并发运行时多个区域的最佳做法。

尽可能使用协作同步构造

并发运行时提供许多不需要外部同步对象的并发安全构造。 例如,concurrency::concurrent_vector 类可提供并发安全的追加和元素访问操作。 在这里,并发安全意味着指针或迭代器始终有效。 它不保证元素初始化或特定的遍历顺序。 但是,如果你需要对资源进行独占访问,则运行时可提供 concurrency::critical_section、concurrency::reader_writer_lock 和 concurrency::event 类。 这些类型以协作的形式工作;因此,当第一个任务等待数据时,任务计划程序可将处理资源重新分配到另一个上下文。 如果可能,请使用这些同步类型而不是其他同步机制(例如 Windows API 提供的机制),这些机制不是以协作的方式工作。 

避免不听从安排的长时间运行的任务

由于任务计划程序以协作的方式工作,它不会在任务之间提供公平性。 因此,一个任务可以阻止其他任务启动。 尽管在某些情况下这是可以接受的,但在其他情况下,这可能会导致死锁或资源枯竭。

以下示例执行的任务超过了分配的处理资源数。 第一个任务不听从任务计划程序的安排,因此第二个任务在第一个任务完成之前不会启动。

// cooperative-tasks.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// Data that the application passes to lightweight tasks.
struct task_data_t
{
   int id;  // a unique task identifier.
   event e; // signals that the task has finished.
};

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

int wmain()
{
   // For illustration, limit the number of concurrent 
   // tasks to one.
   Scheduler::SetDefaultSchedulerPolicy(SchedulerPolicy(2, 
      MinConcurrency, 1, MaxConcurrency, 1));

   // Schedule two tasks.

   task_data_t t1;
   t1.id = 0;
   CurrentScheduler::ScheduleTask(task, &t1);

   task_data_t t2;
   t2.id = 1;
   CurrentScheduler::ScheduleTask(task, &t2);

   // Wait for the tasks to finish.

   t1.e.wait();
   t2.e.wait();
}

该示例产生下面的输出:

1: 250000000 1: 500000000 1: 750000000 1: 1000000000 2: 250000000 2: 500000000 2: 750000000 2: 1000000000

可通过多种方式在两个任务之间实现协作。 一种方式是在长时间运行的任务中偶尔听从任务计划程序的安排。 以下示例修改 task 函数,以调用 concurrency::Context::Yield 方法来让任务计划程序先安排运行另一个任务。

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();

         // Yield control back to the task scheduler.
         Context::Yield();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

该示例产生下面的输出:

1: 250000000
2: 250000000
1: 500000000
2: 500000000
1: 750000000
2: 750000000
1: 1000000000
2: 1000000000

ontext::Yield 方法仅让步于当前线程所属的计划程序上的另一个活动线程、轻量级任务或另一个操作系统线程。 此方法不会对 concurrency::task_group 或 concurrency::structured_task_group 对象中计划运行但尚未开始的工作做出让步。

还可以通过其他方式在长时间运行的任务之间实现协作。 可将大任务分解为较小的子任务。 还可以在长时间运行任务期间启用过度订阅。 可以通过过度订阅创建比可用硬件线程数更多的线程。 当长时间运行的任务存在严重的延迟时(例如,从磁盘或网络连接读取数据),过度订阅特别有用。

使用过度订阅来抵消阻塞或高延迟的操作

并发运行时可提供同步基元(如 concurrency::critical_section),以便任务能够以协作方式停滞和互相做出让步。 如果一个任务以协作方式阻塞或让步,当第一个任务等待数据时,任务计划程序可将处理资源重新分配到另一个上下文。

在某些情况下,无法使用并发运行时提供的协作阻塞机制。 例如,使用的外部库可能使用不同的同步机制。 另一个示例是执行可能存在严重延迟的操作,例如,使用 Windows API ReadFile 函数从网络连接读取数据时。 在这种情况下,过度订阅能使其他任务在另一个任务空闲时运行。 可以通过过度订阅创建比可用硬件线程数更多的线程。

考虑以下 download 函数,它从给定的 URL 下载文件。 此示例使用 concurrency::Context::Oversubscribe 方法临时增加活动线程的数量。

// Downloads the file at the given URL.
string download(const string& url)
{
   // Enable oversubscription.
   Context::Oversubscribe(true);

   // Download the file.
   string content = GetHttpFile(_session, url.c_str());
   
   // Disable oversubscription.
   Context::Oversubscribe(false);

   return content;
}

由于 GetHttpFile 函数执行可能存在延迟的操作,过度订阅能使其他任务在当前任务等待数据时运行。

尽可能使用并发内存管理函数

如果你具有经常分配生存期相对较短的小型对象的精细任务时,可使用内存管理函数 concurrency::Alloc 和 concurrency::Free。 并发运行时为每个正在运行的线程保留单独的内存缓存。 Alloc 和 Free 函数从这些缓存中分配和释放内存,而不使用锁或内存屏障。


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

相关文章:

  • 【MySQL】C语言连接MySQL数据库3——事务操作和错误处理API
  • plsql 高版本用不了 expaste 插件 问题
  • NLP--一起学习Word Vector【实践】
  • TDengine 与北微传感达成合作,解决传统数据库性能瓶颈
  • nginx的配置
  • 数据挖掘示例
  • 一文掌握Kubernates核心组件,构建智能容器管理集群
  • 业务开发常见问题-并发工具类
  • ue5实现数字滚动增长
  • 分布式日志有哪些?
  • 深入理解 Java 集合框架
  • eachers中的树形图在点击其中某个子节点时关闭其他同级子节点
  • Mac 下安装FastDFS
  • JVM的内存模型是什么,每个区域的作用是什么,以及面试题(含答案)
  • 在Java中,需要每120分钟刷新一次的`assetoken`,并且你想使用Redis作为缓存来存储和管理这个令牌
  • 微服务-CAP和AKF拆分原则
  • 大语言模型数据类型与环境安装(llama3模型)
  • 【vuejs】富文本框输入的字符串按规则解析填充表单
  • [C++进阶数据结构]红黑树(半成品)
  • oneplus3t-android_framework
  • 中间件-概念
  • 高翔【自动驾驶与机器人中的SLAM技术】学习笔记(十二)拓展图优化库g2o(一)框架
  • 3种方法,教你用Pytest更改自动化测试用例执行顺序
  • 192×144像素是几寸照片?如何手机拍照制作
  • 【python实操】python小程序之参数化以及Assert(断言)
  • General Purpose I/O Ports and Peripheral I/O Lines (Ports)