Pipeline处理Dataflow

https://www.cnblogs.com/CoderAyu/p/9757389.html

.Net Core中利用TPL(任务并行库)构建Pipeline处理Dataflow

在学习的过程中,看一些一线的技术文档很吃力,而且考虑到国内那些技术牛人英语都不差的,要向他们看齐,所以每天下班都在疯狂地背单词,博客有些日子没有更新了,见谅见谅 Smile with tongue out

什么是TPL?

Task Parallel Library (TPL), 在.NET Framework 4微软推出TPL,并把TPL作为编写多线程和并行代码的首选方式,但是,在国内,到目前为止好像用的人并不多。(TPL)是System.Threading和System.Threading.Tasks命名空间中的一组公共类型和API 。TPL的目的是通过简化向应用程序添加并行性和并发性的过程来提高开发人员的工作效率,TPL动态地扩展并发度,以最有效地使用所有可用的处理器。通过使用TPL,您可以最大限度地提高代码的性能,让我们专注于程序本身而不用去关注负责的多线程管理。

出自: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl

为什么使用TPL?

在上面介绍了什么是TPL,可能大家还是云里雾里,不知道TPL的好处到底是什么。

我在youtube上找到了一个优秀的视频,讲述的是TPL和Thread的区别,我觉得对比一下,TPL的优势很快就能体现出来,如果大家能打开的话建议大家一定要看看。

地址是:https://www.youtube.com/watch?v=No7QqSc5cl8

现如今,我们的电脑的CPU怎么也是2核以上,下面假设我的电脑是四核的,我们来做一个实验。

使用Thread

代码中,如果使用Thread来处理任务,如果不做特出的处理,只是thread.Start(),监测电脑的核心的使用情况是下面这样的。

TIM截图20181003221820

每一条线代表CPU某个核心的使用情况,明显,随着代码Run起来,其实只有某一个核心的使用率迅速提升,其他核心并无明显波动,为什么会这样呢?

TIM截图20181003221925

原来,默认情况下,操作系统并不会调用所有的核心来处理任务,即使我们使用多线程,其实也是在一个核心里面运行这些Thread,而且Thread之间涉及到线程同步等问题,其实,效率也不会明显提高。

使用TPL

在代码中,引入了TPL来处理相同的任务,再次监视各个核心的使用情况,效果就变得截然不同,如下。

TIM截图20181003222605

可以看到各个核心的使用情况都同时有了明显的提高。

TIM截图20181003222044

说明使用TPL后,不再是使用CPU的某个核心来处理任务了,而是TPL自动把任务分摊给每个核心来处理,处理效率可想而知,理论上会有明显提升的(为什么说理论上?和使用多线程一样,各个核心之间的同步管理也是要占用一定的效率的,所以对于并不复杂的任务,使用TPL可能适得其反)。

实验结果出自https://www.youtube.com/watch?v=No7QqSc5cl8

看了这个实验讲解,是不是理解了上面所说的这句。

TPL的目的是通过简化向应用程序添加并行性和并发性的过程来提高开发人员的工作效率,TPL动态地扩展并发度,以最有效地使用所有可用的处理器。

所以说,使用TPL 来处理多线程任务可以让你不必吧把精力放在如何提高多线程处理效率上,因为这一切,TPL 能自动地帮你完成。

TPL Dataflow?

TPL处理Dataflow是TPL强大功能中的一种,它提供一套完整的数据流组件,这些数据流组件统称为TPL Dataflow Library,那么,在什么场景下适合使用TPL Dataflow Library呢?

官方举的一个 栗子 再恰当不过:

例如,通过TPL Dataflow提供的功能来转换图像,执行光线校正或防红眼,可以创建管道数据流组件,管道中的每个功能可以并行执行,并且TPL能自动控制图像流在不同线程之间的同步,不再需要Thread 中的Lock。

TPL数据流库由Block组成,Block是缓冲和处理数据的单元,TPL定义了三种最基础的Block。

source blocks(System.Threading.Tasks.Dataflow.ISourceBlock ),源块充当数据源并且可以从中读取。

target blocks(System.Threading.Tasks.Dataflow.ITargetBlock ),目标块充当数据接收器并可以写入。

propagator blocks(System.Threading.Tasks.Dataflow.IPropagatorBlock <TInput,TOutput>),传播器块充当源块和目标块,并且可以被读取和写入。它继承自ISourceBlock 和ITargetBlock 。

还有其他一些个性化的Block,但其实他们都是对这三种Block进行一些扩充,可以结合下面的代码来理解这三种Block.

Code Show

1.source block 和 target block 合并成propagator block.

复制代码

private IPropagatorBlock<string, Dictionary<int, string>> Process1()

{

var bufferBlock = new BufferBlock<Dictionary<int, string>>();

var actionBlock = new ActionBlock(x =>

{

Console.WriteLine($"Process1 处理中:{x}");

Thread.Sleep(5000);

var dic = new Dictionary<int, string> { { 0, x } };

dic.Add(1, "Process1");

bufferBlock.Post(dic);

}, new ExecutionDataflowBlockOptions

{

MaxDegreeOfParallelism = maxDegreeOfParallelism

});

actionBlock.Completion.ContinueWith(
=>

{

Console.WriteLine($"Process1 Complete,State{_.Status}");

bufferBlock.Complete();

});

return DataflowBlock.Encapsulate(actionBlock, bufferBlock);

}

复制代码

可以看到,我定义了BufferBlock和ActionBlock,它们分别继承于ISourceBlock 和 ITargetBlock ,所以说,他们其实就是源块和目标块,在new actionBlock()中传入了一个Action,该Action就是该Block所执行的任务。 最后,DataflowBlock.Encapsulate(actionBlock, bufferBlock)把源块和目标块合并成了一个传递块。

2.TransformBlock

复制代码

private IPropagatorBlock<Dictionary<int, string>, Dictionary<int, string>> Process2()

{

var block = new TransformBlock<Dictionary<int, string>, Dictionary<int, string>>(dic =>

{

Console.WriteLine($"Process2 处理中:{dic.First().Value}");

Thread.Sleep(5000);

dic.Add(2, "Process2");

return dic;

}, new ExecutionDataflowBlockOptions

{

MaxDegreeOfParallelism = _maxDegreeOfParallelism

}

);

        block.Completion.ContinueWith(_ =>
{
Console.WriteLine($"Process2 Complete,State{_.Status}");
}); return block;
}

复制代码

TransfromBlock继承了IPropagatorBlock,所以它本身就是一个传递块,所以它除了要处理出入数据,还要返回数据,所以给new TransformBlock()中传入的是Func<TInput, TOutput>而不是Action.

3.TargetBlock来收尾

复制代码

private ITargetBlock<Dictionary<int, string>> Process3()

{

var actionBlock = new ActionBlock<Dictionary<int, string>>(dic =>

{

Console.WriteLine($"Process3 处理中:{dic.First().Value}");

Thread.Sleep(5000);

dic.Add(3, "Process3");

Console.WriteLine("Dic中的内容如下:");

foreach (var item in dic)

{

Console.Write($"{item.Key}:{item.Value}||");

}

Console.WriteLine();

}, new ExecutionDataflowBlockOptions

{

MaxDegreeOfParallelism = _maxDegreeOfParallelism

});

return actionBlock;

}

复制代码

TargetBlock只能写入并处理数据,不能读取,所以TargetBlock适合作为Pipeline的最后一个Block。

4.控制每个Block的并行度

在在构造TargetBlock(包括其子类)的时候,可以传入ExecutionDataflowBlockOptions参数,ExecutionDataflowBlockOptions对象里面有一个MaxDegreeOfParallelism属性,通过改制,可以控制该Block的同时处理任务的数量(可以理解成线程数)。

new ExecutionDataflowBlockOptions

{

MaxDegreeOfParallelism = _maxDegreeOfParallelism

}

5.构建Pipeline,连接Block

复制代码

public Task Builder()

{

_startBlock = Process1();

var process2Block = Process2();

var process3Block = Process3();

        _startBlock.LinkTo(process2Block, new DataflowLinkOptions() { PropagateCompletion = true });

        process2Block.LinkTo(process3Block, new DataflowLinkOptions() { PropagateCompletion = true });

        process3Block.Completion.ContinueWith(_ =>
{
Console.WriteLine($"Process3 Complete,State{_.Status}");
Console.WriteLine("所有任务处理完成");
}); return process3Block.Completion;
}

复制代码

通过

ISourceBlock.LinkTo(ITargetBlock target, DataflowLinkOptions linkOption)

方法,可以把Block连接起来,即构建Pipeline,当DataflowLinkOptions对象的PropagateCompletion属性为true时,SorceBlock任务处理完成是,会把TargetBlock也标记为完成。

Block被标记为Complete 后,无法传入新的数据了,即不能再处理新的任务了。

6.Pipeline的运行

复制代码

public void Process(string[] inputs)

{

if (inputs == null)

return;

foreach (var input in inputs)

{

_startBlock.Post(input);

}

_startBlock.Complete();

}

复制代码

Pipeline构建好后,我们只需要给第一个Block传入数据,该数据就会在管道内流动起来了,所有数据传入完成后,调用Block的Complete方法,把该Block标记为完成,就不可以再往里面Post数据了。

完整代码如下:

复制代码

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using System.Threading.Tasks.Dataflow;

namespace Tpl.Dataflow

{

public class Pipeline

{

IPropagatorBlock<string, Dictionary<int, string>> _startBlock;

private int _maxDegreeOfParallelism;

    public Pipeline(int maxDegreeOfParallelism)
{
_maxDegreeOfParallelism = maxDegreeOfParallelism;
} public void Process(string[] inputs)
{
if (inputs == null)
return;
foreach (var input in inputs)
{
_startBlock.Post(input);
}
_startBlock.Complete();
} public Task Builder()
{
_startBlock = Process1();
var process2Block = Process2();
var process3Block = Process3(); _startBlock.LinkTo(process2Block, new DataflowLinkOptions() { PropagateCompletion = true }); process2Block.LinkTo(process3Block, new DataflowLinkOptions() { PropagateCompletion = true }); process3Block.Completion.ContinueWith(_ =>
{
Console.WriteLine($"Process3 Complete,State{_.Status}");
Console.WriteLine("所有任务处理完成");
}); return process3Block.Completion;
} private IPropagatorBlock<string, Dictionary<int, string>> Process1()
{
var bufferBlock = new BufferBlock<Dictionary<int, string>>();
var actionBlock = new ActionBlock<string>(x =>
{
Console.WriteLine($"Process1 处理中:{x}");
Thread.Sleep(5000);
var dic = new Dictionary<int, string> { { 0, x } };
dic.Add(1, "Process1");
bufferBlock.Post(dic);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
});
actionBlock.Completion.ContinueWith(_ =>
{
Console.WriteLine($"Process1 Complete,State{_.Status}");
bufferBlock.Complete();
});
return DataflowBlock.Encapsulate(actionBlock, bufferBlock);
} private IPropagatorBlock<Dictionary<int, string>, Dictionary<int, string>> Process2()
{
var block = new TransformBlock<Dictionary<int, string>, Dictionary<int, string>>(dic =>
{
Console.WriteLine($"Process2 处理中:{dic.First().Value}");
Thread.Sleep(5000);
dic.Add(2, "Process2");
return dic;
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
}
); block.Completion.ContinueWith(_ =>
{
Console.WriteLine($"Process2 Complete,State{_.Status}");
}); return block;
} private ITargetBlock<Dictionary<int, string>> Process3()
{
var actionBlock = new ActionBlock<Dictionary<int, string>>(dic =>
{
Console.WriteLine($"Process3 处理中:{dic.First().Value}");
Thread.Sleep(5000);
dic.Add(3, "Process3");
Console.WriteLine("Dic中的内容如下:");
foreach (var item in dic)
{
Console.Write($"{item.Key}:{item.Value}||");
}
Console.WriteLine();
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = _maxDegreeOfParallelism
});
return actionBlock;
}
}

}

复制代码

Main方法如下:

复制代码

static void Main(string[] args)

{

Console.WriteLine("请输入管道并发数:");

if (int.TryParse(Console.ReadLine(), out int max))

{

var pipeline = new Pipeline(max);

var task = pipeline.Builder();

pipeline.Process(new[] { "码", "农", "阿", "宇" });

task.Wait();

Console.ReadKey();

}

}

复制代码

测试运行如图:

image

我来解释一下,为什么是这么运行的,因为把管道的并行度设置为2,所以每个Block可以同时处理两个任务,所以,如果给管道传入四个字符 ,每个字符作为一个任务,假设传入 “码农阿宇”四个任务,会时这样的一个过程…..

码 农 两个首先进入Process1,

处理完成后,码 农 两个任务流出,

Process1位置空出来, 阿 宇 两个任务流入 Process1,

码 农 两个任务流向 Process2,

阿 宇 从 Process1 处理完成后流出,此时Process1任务完成

码 农 流出 Process2 ,同时 阿 宇 流入 Process2 ……

依此类推….

该项目Github地址: https://github.com/liuzhenyulive/Tpl-Dataflow-Demo

参考文献:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library

Pipeline处理Dataflow的更多相关文章

  1. .Net Core中利用TPL(任务并行库)构建Pipeline处理Dataflow

    在学习的过程中,看一些一线的技术文档很吃力,而且考虑到国内那些技术牛人英语都不差的,要向他们看齐,所以每天下班都在疯狂地背单词,博客有些日子没有更新了,见谅见谅 什么是TPL? Task Parall ...

  2. 利用TPL(任务并行库)构建Pipeline处理Dataflow

    https://www.cnblogs.com/CoderAyu/p/9757389.html

  3. Python高级编程之生成器(Generator)与coroutine(三):coroutine与pipeline(管道)和Dataflow(数据流_

    原创作品,转载请注明出处:点我 在前两篇文章中,我们介绍了什么是Generator和coroutine,在这一篇文章中,我们会介绍coroutine在模拟pipeline(管道 )和控制Dataflo ...

  4. SSIS Data Flow 的 Execution Tree 和 Data Pipeline

    一,Execution Tree 执行树是数据流组件(转换和适配器)基于同步关系所建立的逻辑分组,每一个分组都是一个执行树的开始和结束,也可以将执行树理解为一个缓冲区的开始和结束,即缓冲区的整个生命周 ...

  5. 十分钟了解分布式计算:Google Dataflow

    介绍 Google Cloud Dataflow是一种构建.管理和优化复杂数据处理流水线的方法,集成了许多内部技术,如用于数据高效并行化处理的Flume和具有良好容错机制流处理的MillWheel.D ...

  6. A trip through the Graphics Pipeline 2011_13 Compute Shaders, UAV, atomic, structured buffer

    Welcome back to what’s going to be the last “official” part of this series – I’ll do more GPU-relate ...

  7. A trip through the Graphics Pipeline 2011_03

    At this point, we’ve sent draw calls down from our app all the way through various driver layers and ...

  8. A trip through the Graphics Pipeline 2011_01

    It’s been awhile since I posted something here, and I figured I might use this spot to explain some ...

  9. The Dataflow Model 论文

    A Practical Approach to Balancing Correctness, Latency, and Cost in MassiveScale, Unbounded, OutofOr ...

随机推荐

  1. Java结对编程之挑战出题

    Java结对编程之挑战出题 需求分析 需求 对于挑战出题来说最主要的就是要产生的式子并将重复的式子去掉. 设计思路 具体的思路: 思路一: 原先我打算用集合中的元素的不重复性进行去重,这种思路的好处就 ...

  2. Git使用的常用命令

    一.git工作流程 Workspace工作区:是当前工作目录,可以在此目录编辑文件 Index缓存区:add指令,保存文件的改动 Repository仓库:commit指令,将多次的文件改动最后提交 ...

  3. 2 Powershell与Cmd以及Unix/Linux Shell

    上篇文章我说道,windows为了改变用户对其console界面的诟病,于是就从windows   vista开始,计划要改变这种局面,于是就有 了Powershell的出现. 1.兼容shell命令 ...

  4. ubuntu 14.04 建立wifi热点

    昨天突然想起来我可以用笔记本搞一个热点这样我的手机就不用上流量了,但是手机死活搜不到建好的信号,目前的解决方案如下: 直接用ubuntu自带的创建wifi网络功能是不好使的,因为android系统不支 ...

  5. php源码之appveyor

    打开appveyor目录 是4个bat文件 1. .bat是什么? bat文件是dos下的批处理文件.批处理文件是无格式的文本文件,它包含一条或多条命令. 它的文件扩展名为.bat或.cmd 在命令提 ...

  6. Apache顶级项目 Calcite使用介绍

    什么是Calcite Apache Calcite是一个动态数据管理框架,它具备很多典型数据库管理系统的功能,比如SQL解析.SQL校验.SQL查询优化.SQL生成以及数据连接查询等,但是又省略了一些 ...

  7. HTTP Status 500 - javax.el.PropertyNotFoundException: Property 'lkmId' not found on type cn.itcast.entity.LinkMan

    报错 type Exception report message javax.el.PropertyNotFoundException: Property 'lkmId' not found on t ...

  8. MySQL5.7版本开启二进制日志是log_bin、bin-log 还是 bin_log ?

    已Mac系统为例,文件:/usr/local/mysql/support-files/my-default.cnf 是mysql的默认配置文件,你可以直接修改这个文件但是不推荐,你可以在/etc/my ...

  9. 分享知识-快乐自己:能使 Oracle 索引失效的六大限制条件

    Oracle 索引的目标是避免全表扫描,提高查询效率,但有些时候却适得其反. 例如一张表中有上百万条数据,对某个字段加了索引,但是查询时性能并没有什么提高,这可能是 oracle 索引失效造成的.or ...

  10. APP耗电量专项测试整理

    Android: (使用batterystats) 方法: 手机自带的电量监控.GT 命令(5.0以上系统才可以): 1.下载historian.py脚本,下载地址:https://github.co ...