草庐IT

C#并发编程-3 并行编程基础

一团静火 2023-03-28 原文

如果程序中有大量的计算任务,并且这些任务能分割成几个互相独立的任务块,那就应该使用并行编程。

并行编程用于分解计算密集型的任务片段,并将它们分配给多个线程。这些并行处理方法只适用于计算密集型的任务。

一 数据的并行处理

如果有一批数据,需要对每个数据进行相同的操作,其操作是计算密集型的,需要耗费一定的时间。

Parallel 类型有 ForEach 方法可以解决上述问题。

下例使用了一批矩阵,对每一个矩阵都进行旋转,Matrix类的Rotate方法是计算密集型的任务。

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
    Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

在某些情况下需要尽早结束这个循环,例如发现了无效值时。下例反转每一个矩阵,但是如果发现有无效的矩阵,则中断循环:

void InvertMatrices(IEnumerable<Matrix> matrices)
{
    Parallel.ForEach(matrices, (matrix, state) =>
    {
        if (!matrix.IsInvertible)
            state.Stop();
        else
            matrix.Invert();
    });
}

更常见的情况是可以取消并行循环,这与结束循环不同。结束(stop)循环是在循环内部进行,

而取消(cancel)循环是在循环外部进行的。例如,点击“取消”按钮可以取消一个 CancellationTokenSource,以取消并行循环,如下:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees,CancellationToken token)
{
    Parallel.ForEach(matrices,new ParallelOptions { CancellationToken = token }, matrix => matrix.Rotate(degrees));
}

注意,每个并行任务可能都在不同的线程中运行,因此必须保护对共享的状态。

 

二 并行聚合

使用Parallel,在并行操作结束时,可以根据需要聚合结果,包括累加和、平均值等。

Parallel 类通过局部值(local value)的概念来实现聚合,局部值就是只在并行循环内部存在的变量。

这意味着循环体中的代码可以直接访问值,不需要担心同步问题。

循环中的代码使用 LocalFinally 委托来对每个局部值进行聚合。

需要注意的是,localFinally 委托需要以同步的方式对存放结果的变量进行访问。

下面是一个并行求累加和的例子:

//注意,这不是最高效的实现方式,只是举个例子,说明用锁来保护共享状态。
static int ParallelSum(IEnumerable<int> values)
{
    object mutex = new object();
    int result = 0;
    Parallel.ForEach(
        source: values,
        localInit: () => 0,
        body: (item, state, localValue) => localValue + item,
        localFinally: localValue =>
        {
            lock (mutex)
                result += localValue;
        }
    );
    return result;
}

并行 LINQ 对聚合的支持,比 Parallel 类更加易用:

static int ParallelSum(IEnumerable<int> values)
{
    return values.AsParallel().Sum();
}

PLINQ 本身支持很多常规操作(例如求累加和)。大多数情况下PLINQ 对聚合的支持更有表现力,代码也更少。

PLINQ也可通过 Aggregate 实现通用的聚合功能:

static int ParallelSum(IEnumerable<int> values)
{
    return values.AsParallel().Aggregate(
        seed: 0,
        func: (sum, item) => sum + item
    );
}

 

三 并行调用

如果需要并行调用一批方法,并且这些方法(大部分)是互相独立的。

Parallel 类有一个简单的成员 Invoke,可用于这种场合。

下面的例子将一个数组分为两半,并且分别独立处理:

static void ProcessArray(double[] array)
{
    Parallel.Invoke(
        () => ProcessPartialArray(array, 0, array.Length / 2),
        () => ProcessPartialArray(array, array.Length / 2, array.Length)
    );
}
static void ProcessPartialArray(double[] array, int begin, int end)
{
    // 计算密集型的处理过程 ...
}

如果在运行之前都无法确定调用的方法数量,就可以在 Parallel.Invoke 函数中输入一个委托数组,Parallel.Invoke 也支持取消操作:

static void DoAction20Times(Action action, CancellationToken token)
{
    Action[] actions = Enumerable.Repeat(action, 20).ToArray();
    Parallel.Invoke(new ParallelOptions { CancellationToken = token }, actions);
}

对于简单的并行调用,Parallel.Invoke 是一个非常不错的解决方案。

但在以下两种情况中使用 Parallel.Invoke 并不是很合适:

要对每一个输入的数据调用一个操作(改用Parallel.Foreach),或者每一个操作产生了一些输出(改用并行 LINQ)。

 

四 并行LINQ

LINQ 可以实现在序列上”拉取“数据的运算。并行LINQ(PLINQ)扩展了 LINQ,以支持并行处理。

PLINQ 非常适用于数据流的操作,一个数据队列作为输入,一个数据队列作为输出。

下面简单的例子将序列中的每个元素都乘以2:

static IEnumerable<int> MultiplyBy2(IEnumerable<int> values)
{
    return values.AsParallel().Select(item => item * 2);    //实际应用中,计算工作量要大得多
}

按照并行 LINQ 的默认方式,这个例子中输出数据队列的次序是不固定的。

我们可以指明要求保持原来的次序。下面的例子也是并行执行的,但保留了数据的原有次序:

static IEnumerable<int> MultiplyBy2(IEnumerable<int> values)
{
    return values.AsParallel().AsOrdered().Select(item => item * 2);
}

Parallel 类可适用于很多场合,但是在做聚合或进行数据序列的转换时,PLINQ 的代码更加简洁。

PLINQ 为各种各样的操作提供了并行的版本,包括过滤(Where)、投影(Select)以及各种聚合运算,

例如 Sum、Average 和更通用的 Aggregate。一般来说,对常规 LINQ 的所有操作都可以通过并行方式对 PLINQ 执行。

 

 

有关C#并发编程-3 并行编程基础的更多相关文章

  1. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  2. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  3. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  6. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  7. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  8. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  9. ruby - 带括号和 splat 运算符的并行赋值 - 2

    我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

  10. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

    假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

随机推荐