一.基于任务的程序设计

共享内存多核OS和分布式内存OS

共享内存多核OS-一个微处理器由多个内核组成,且每个内核共享一段私有内存;

分布式内存OS-- 由多个微处理器组成,每个微处理器可以有自己的私有内存,微处理器可以位于不同的计算机上,每个计算机可以有不同的通信信道消息传递接口(MPI):运行在分布式内存计算机系统上的并行应用程序所使用的最流行的通信协议;

并行程序设计和多核程序设计

并行程序设计是指同一时刻运行多条指令,编写的代码能够充分利用底层硬件提供的并行执行能力;

多核程序设计能够充分利用多个执行内核并行运行多个指令;

硬件线程和软件线程

物理内核(physical core)--真正独立的处理单元,多个物理内核使多条指令能够同时并行的运行。每一个物理内核可提供多个硬件线程(亦称逻辑内核或逻辑处理器);

对称多线程(simultaneous multithreading,SMT):使用超线程计技术(HT)使微处理器在每个物理内核上提供多份架构状态,从而获得了 物理内核数X架构状态数 个硬件线程;

软件线程:每一个软件线程与其父进程分享一个私有的唯一的内存空间,但每一个软件线程有自己的栈、寄存器和私有局部存储区域;可以将硬件线程比作泳道,软件线程比作游泳者;

负载均衡:将软件线程的任务分发在多个硬件线程的操作,通过负载均衡,工作负载可以公平地分配在硬件线程之间。实现负载均衡取决于应用程序的并行程度、工作负载、软件线程数、可用的硬件线程以及负载均衡策略;

Amdahl法则

预测多处理器系统的最大理论性能提升(加速比)

公式:最大加速比=1/((1-P)+(p/N))

P指能够完成并行运行的代码比例

N指可用的计算单元数(处理器或物理内核数)

Gustafson法则

通过问题的大小来测量在固定时间内的可以执行的工作量;

总工作量(单元数)=S+(N*P);

S表示一次顺序执行完成的工作单元数;

P表示每一部分能够完全并行执行的工作单元数;

N表示可用的执行单元数(处理器数或物理内核数)

重量级并发模型和轻量级并发模型

重量级并发模型(多线程编程模型):编写复杂的多线程代码;将算法分解为多个线程、协调各个代码单元、在代码单元之间共享信息以及收集运算结果等任务;并且多线程模型过于复杂,难以应对多核革命;由于框架层次缺乏对多线程范围的支持,多线程需要做大量处理,这会导致代码复杂难以理解;

轻量级并发模型:减少了在不同逻辑内核上创建和执行代码所需要的总开销,并不只是关注不同逻辑内核之间的作业调度,还在框架级别添加对多线程访问的支持;.net framework 4.0实现了该模型;

交错并发,并发和并行

交错并发(interleaved concurrency):一次执行一个线程的指令,两个线程的指令交错执行

并发(concurrency) :两个线程的指令同时执行

并行化要求:对需要完成的工作进行划分、并发的运行处理划分的部分、并且能够整合运行结果;对一个问题进行并行化就会产生并发性;

多核并行程序设计原则

按照并行的方式思考;

使用抽象编程(TPL任务并行库);

按照任务(事情)编程,而不是按照线程(CPU内核)线程--通过TPL,可以编写代码实现基于任务的的设计,而不用关注底层的线程;

设计的时候要考虑关闭并发的情形;

避免使用锁--TPL在很多复杂的情况下使得避免使用重量级的锁更加简单,TPL还提供了新的轻量级的同步机制;

利用为帮助并发而设计的工具和库;

使用可扩展的内存分配器;

设计的时候要考虑增长的工作负载而扩展;

CoreInfo工具  --查看处理器信息程序

二.命令式数据并行

TPL支持数据并行(对每一份数据执行相同的操作)、任务并行(并发的运行不同的操作)和流水线(任务并行和数据并行的结合体);

Parallel.Invoke ----对给定的独立任务提供潜在的并行执行;

需要传入一个要并行执行的Action委托的参数数组;方法没有特定的执行顺序,只有在所有方法都执行完之后才会返回;

优势:并行运行很多方法的简单方式,不用考虑任务和线程的问题;

循环并行化----Parallel.For和ForEach,不支持浮点数和步进。无法保证迭代执行的顺序;

Parallel.For ----为固定数目的独立For循环迭代提供了负载均衡的潜在的并行执行;负载均衡的执行会尝试将工作分发在不同任务中,这样所有的任务在大部分时间内都可以保持繁忙。负载均衡总是试图减少任务的闲置时间。

Parallel.ForEach----为固定数目的独立For循环迭代提供了负载均衡的潜在的并行执行;支持自定义分区器,让你可以完全掌控数据并发;提供了20种重载方法,source参数表示分区器;

利用一个范围整数作为一组数据,通过一个分区器,把数据分成一组数据块。每一块的数据都通过循环的方式处理,而这些循环都是并行的。

在并行循环中使用分区:Partitioner.Create(1, NUM_AES_KEYS + 1);

根据内核数目优化分区:Environment.ProcessorCount 获取逻辑内核的个数;

Partitioner.Create(1,NUM_AES_KEYS, ((int)(NUM_AES_KEYS / Environment.ProcessorCount) + 1))

使用IEnumerable接口的数据源作为分区器;

从并行循环中退出:

在参数中使用ParallelLoopState,就可以使用loopState.Break()或者loopState.Stop()进行退出。其中的差别在于,假设调用Break的时候正在处理迭代100,那么可以保证小于100的迭代都被执行,而Stop不保证这个而是告诉并行循环应尽快停止执行。ParallelLoopResult作为返回值,可以知道是否是正常完成或者被Break的;

捕获并行循环的异常:

try
{
    loopResult =
Parallel.ForEach(inputData,
    (int number, ParallelLoopState
loopState) =>
    {        throw new Exception();  });
}
catch (AggregateException ex)
{
    foreach (Exception innerEx in ex.InnerExceptions)
    {
        Debug.WriteLine(innerEx.ToString());
    }
}

指定并行度:ParallelOption用于修改最大并行度。

var parallelOptions = new ParallelOptions();
    parallelOptions.MaxDegreeOfParallelism = maxDegree;

Parallel类型下的For、 Foreach、Invoke方法都是启动Task来完成并行操作;但不等同于Task的默认行为,Parallel简化了Task操作,调用者线程在调用Parallel类方法时会被阻塞,调用者线程会一直等到线程池的相关工作全部完成。而Task不会;并行编程虽然在后台使用Task进行管理,但并不意味着它等同于异步。

//主线程被阻滞

static void Main()

{

//在这里也可以使用Invoke方法

Parallel.For(0, 1, (i) =>

{

while (true) { }

});

Console.WriteLine("主线程即将结束");

Console.ReadKey();

}

Parallel中为For和Foreach方法提供多种重载,它们允许我们在每个任务启动时执行一些初始化操作,在每个任务结束后,又执行一些后续操作,同时允许监视任务的状态;

Public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, Func<TLocal> localInit, Func<TSource, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

重点理解 localInit 和localFinally两个参数:

localInit:如果Parallel新起一个线程去执行任务,它就会执行该委托方法;注是新起一个线程而不是执行一个新的任务;

localFinally:每一个线程结束时,执行一些收尾工作;

三.命令式任务并行

任务是使用底层线程(软件线程)运行的,但任务和线程不是一对一的关系;CLR会创建必要的线程来支持任务执行的需求;默认的任务调度器依赖于底层的线程池引擎;在创建一个任务时,调度器会使用工作窃取队列(work-stealing queue)找到一个合适的线程,然后将任务加入队列;一个Task表示一个异步操作;

TaskStatus状态:初始状态——>TaskStatus.Running状态————>最终状态(如果Task实例有关联的子任务,Task将转变为TaskStatus.WaitingForChildrenToComplete,当子任务都完成后才会进入最终状态)

Task.Delay是一个异步静态方法,用于释放线程供其它任务使用。

创建一个Task的四种方式:1.使用TaskFactory类的实例对象调用其StartNew方法,会立即启动任务;

2.Task.Factory.StartNew()方法,和第一种方法类似;      3.实例化一个Task对象,利用Task的构造函数,任务不会立即运行,而是指定Created状态。调用Start()方法来启动任务;

4..Net4.5新增,调用Task.Run静态方法,立即启动任务;

创建一个Task时,通过TaskCreationOptions标志来控制Task的执行方式或行为;

// 摘要: 指定可控制任务的创建和执行的可选行为的标志。

[Flags]

public enum TaskCreationOptions

{

// 摘要: 指定应使用默认行为。

None = 0,

//摘要:提示TaskScheduler以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。

PreferFairness = 1,// 将任务放到全局队列中,而不是放到一个工作者线程的本地队列中

// 摘要:指定某个任务可能是运行时间长、粗粒度的操作。它会向TaskScheduler提示,过度订阅可能是合理的,并创建一个新线程而不是使用线程池中的线程。

LongRunning = 2,

// 摘要:指定将任务附加到任务层次结构中的某个父级。

AttachedToParent = 4,

//当一个任务内启动一个任务时,指定该任务是普通任务,而不是子任务

DenyChildAttach = 8,

//强制子任务使用默认的任务调度器而不是父任务的调度器

HideScheduler = 16

}

同步任务:任务不一定要使用线程池的线程,也可以使用其它线程。任务也可以同步运行,以相同的线程作为主调线程。RunSynchronously方法;

private static void RunSynchronousTask()
    {
        var t1 = new Task(TaskMethod, "run sync");
        t1.RunSynchronously();
    }

Task.WaitAll静态方法:它阻塞调用线程,直到Task对象数组所有Task对象都完成,其重载方法可以指定要等待的毫秒数,返回一个bool值,表明任务是否在指定时间内完成;也可以使用task.Wait实例方法实现等待;

Task.WaitAny静态方法:它阻塞调用线程,直到Task对象数组任何一个Task对象完成,其重载方法可以指定要等待的毫秒数,返回一个Int32数组索引值,指明完成的是哪一个Task对象。如果发生超时,方法将返回-1。

取消标记(cancellation token):主线程调用CancellationTokenSource的Cancel方法进行中断任务的执行,会使异步操作方法中CancellationToken实例参数的ThrowIfCancellationRequested方法抛出OperationCanceledException异常,使Task实例进入TaskStatus.Canceled状态,并使IsCanceled=true;

Task.Result获取任务的异步操作返回的结果;Task.Result属性会内部调用Wait;

链式任务:任务t1产生一个结果,任务t2需要t1的结果作为输入才能开始处理异步操作;

延续(continuation):如果链式任务重串联了多个任务,为了避免编写太多代码来检查前一个任务是否成功完成并且调度了一个新的任务;

可以在任何任务实例上调用ContinueWith方法创建一个延续,该方法在这个任务成功结束执行后执行。

在创建一个Task作为另一个Task的延续时,可以指定一个TaskContinuationOptions参数,用于控制延续另一个任务的任务调度和执行的可选行为;

在使用多任务延续时,不能使用的标志:NotOnRanToCompletion,NotOnFaulted,NotOnCanceled,OnlyOnCanceled,OnlyOnFaulted,OnlyOnRanToCompletion;

任务层次结构:一个任务启动一个新任务时(一般新任务在当前任务的内部创建),就启动了一个父/子层次结构;取消父任务,也会取消子任务;

C#并行编程(1)的更多相关文章

  1. C#并行编程系列-文章导航

    菜鸟初步学习,不对的地方请大神指教,参考<C#并行编程高级教程.pdf> 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 C# ...

  2. Parallel并行编程初步

    Parallel并行编程可以让我们使用极致的使用CPU.并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片.而并行编程则是多CPU核心同时工作.耗时的CPU计算操作选 ...

  3. .Net中的并行编程-2.ConcurrentStack的实现与分析

    在上篇文章<.net中的并行编程-1.基础知识>中列出了在.net进行多核或并行编程中需要的基础知识,今天就来分析在基础知识树中一个比较简单常用的并发数据结构--.net类库中无锁栈的实现 ...

  4. .Net中的并行编程-3.ConcurrentQueue实现与分析

    在上文<.Net中的并行编程-2.ConcurrentQueue的实现与分析> 中解释了无锁的相关概念,无独有偶BCL提供的ConcurrentQueue也是基于原子操作实现, 由于Con ...

  5. C#~异步编程再续~大叔所理解的并行编程(Task&Parallel)

    返回目录 并行这个概念出自.net4.5,它被封装在System.Threading.Tasks命名空间里,主要提供一些线程,异步的方法,或者说它是对之前Thread进行的二次封装,为的是让开发人员更 ...

  6. .NET并行编程实践(一:.NET并行计算基本介绍、并行循环使用模式)

    阅读目录: 1.开篇介绍 2.NET并行计算基本介绍 3.并行循环使用模式 3.1并行For循环 3.2并行ForEach循环 3.3并行LINQ(PLINQ) 1]开篇介绍 最近这几天在捣鼓并行计算 ...

  7. C#并行编程之数据并行

    所谓的数据并行的条件是: 1.拥有大量的数据. 2.对数据的逻辑操作都是一致的. 3.数据之间没有顺序依赖. 运行并行编程可以充分的利用现在多核计算机的优势.记录代码如下: public class ...

  8. OpenMP共享内存并行编程详解

    实验平台:win7, VS2010 1. 介绍 平行计算机可以简单分为共享内存和分布式内存,共享内存就是多个核心共享一个内存,目前的PC就是这类(不管是只有一个多核CPU还是可以插多个CPU,它们都有 ...

  9. .NET并行编程1 - 并行模式

    设计模式——.net并行编程,清华大学出版的中译本. 相关资源地址主页面: http://parallelpatterns.codeplex.com/ 代码下载: http://parallelpat ...

  10. 【读书笔记】.Net并行编程(三)---并行集合

    为了让共享的数组,集合能够被多线程更新,我们现在(.net4.0之后)可以使用并发集合来实现这个功能.而System.Collections和System.Collections.Generic命名空 ...

随机推荐

  1. Flask简介之简单应用

    Flask 0.Flask简介 Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收ht ...

  2. 【Jenkins学习 】解决jenkins运行磁盘满的问题

    一.背景 今天有同事编译Jenkins的相关Jobs的时候,出现了编译成功,但是输出产物失败的情况,如下图所示: Caused by:java.io.IOException: No space lef ...

  3. MySQL不能启动 Can't start server : Bind on unix socke

    MySQL服务器突然不能启动,查看最后的启动日志如下: 080825 09:38:04 mysqld started080825 9:38:04 [ERROR] Can't start server ...

  4. Spring源码解析(五)循环依赖问题

    引言 循环依赖就是多个类之间互相依赖,比如A依赖B,B也依赖A,如果日常开发中我们用new的方式创建对象,这种循环依赖就会导致不断的在创建对象,导致内存溢出. Spring是怎么解决循环依赖的问题的? ...

  5. 【转】Deep Learning(深度学习)学习笔记整理系列之(三)

    好了,到了这一步,终于可以聊到Deep learning了.上面我们聊到为什么会有Deep learning(让机器自动学习良好的特征,而免去人工选取过程.还有参考人的分层视觉处理系统),我们得到一个 ...

  6. mysql key index区别

    看似有差不多的作用,加了Key的表与建立了Index的表,都可以进行快速的数据查询.他们之间的区别在于处于不同的层面上. Key即键值,是关系模型理论中的一部份,比如有主键(Primary Key), ...

  7. luarocks模块管理工具

    1.简介 该软件包可以安装和更新lua的第三方模块. 2.下载地址 请在 http://luarocks.org/releases/ 页面选择需要的软件包. wget http://luarocks. ...

  8. 《C++ Concurrency in Action》

    http://wiki.jikexueyuan.com/project/cplusplus-concurrency-action/content/resources/resource.html

  9. 《Java入门第二季》第二章 封装

    什么是java中的封装1.封装的概念:隐藏信息.隐藏具体的实现细节. 2.封装的实现步骤: 1)修改属性的可见性,private.2)创建修改器方法和访问器方法,getXXX/setXXX.(未必一定 ...

  10. 前端学习笔记之CSS过渡模块

    阅读目录 一 伪类选择器复习 二 过渡模块的基本使用 三 控制过渡的速度transition-timing-function 四 过渡模块连写 一 伪类选择器复习 注意点: #1 a标签的伪类选择器可 ...