一 并发编程简介

1.1 关于并发和并行

并发和并行的概念:

  • 并发:(Concurrent),在某个时间段内,如果有多个任务执行,即有多个线程在操作时,如果系统只有一个CPU,则不能真正同时进行一个以上的线程,

                它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。

  • 并行:(Parallel),当系统有一个以上CPU时,则线程的操作有可能非并发。

                当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行。

并发和并行是即相似又有区别的两个概念:

  • 并发是指两个或多个事件在同一时间间隔内发生;
  • 并行是指两个或者多个事件在同一时刻发生。

1.2 关于并发编程和并行编程

并发编程的形式:

  • 多线程编程:使用多个线程执行程序;
  • 响应式编程。
  • 异步编程:APM,EAP,TAP。推荐使用 async await关键字的TAP ( Task-based Asynchronous Pattern );

并行编程:把正在执行的大量任务分割成小块,分配个多个同时运行的线程,让它们在不同的核上独立运行。让处理器的利用效率最大化。

二 异步编程简介

2.1 async await关键字

现代的异步.Net程序使用两个关键字,async和await。async关键字加在方法声明上,它的主要目的是使方法内的await关键字生效。

如果async方法有返回值,应返回Task<T>,如果没有返回值,应返回Task。Task类型相当于future,用来在异步方法结束时通知调用程序。

一个异步方法例子:

async Task MethodAsync()
{
var i = 10;
await Task.Delay(1000);
i++;
await Task.Delay(1000);
Trace.WriteLine(i);
}

async方法在开始时以同步方式执行。在async方法内部,await关键字对它的参数执行一个异步等待。

它首先检测操作是否已经完成,如果完成了,就继续以同步方式运行,否则会暂停方法,并返回,留下一个未完成的Task。

一段时间后,操作完成,async方法就恢复,从await出继续往下执行。

2.2 ConfigureAwait ( )

一个 async 方法是由多个同步执行的程序块组成的,每个同步程序块之间由 await 语句分隔。

第一个同步程序块在调用这个方法的线程中运行,那await之后的同步程序块在哪个线程运行呢?

一个常见的情况是,用 await 语句等待一个任务完成,当该方法在 await 处暂停时,就可以捕捉上下文(context)。

如果当前 SynchronizationContext 不为空,这个上下文就是当前SynchronizationContext。如果当前 SynchronizationContext 为空,则这个上下文为当前TaskScheduler。

该方法会在这个上下文中继续运行。一般来说,运行 UI 线程时采用 UI 上下文,处理 ASP.NET 请求时采用 ASP.NET 请求上下文,其他很多情况下则采用线程池上下文。

因此,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在 UI线程中调用 MethodAsync,这个方法的每个同步程序块都将在此 UI 线程上运行。但

是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。

要控制这种行为,可以在 await 中使用 ConfigureAwait 方法,将参数 continueOnCapturedContext设为 false。接下来的代码刚开始会在调用的线程里运行,在被 await 暂停后,

则会在线程池线程里继续运行。

async Task MethodAsync()
{
//在调用线程运行
var i = 10;
await Task.Delay(1000).ConfigureAwait(false);
//在线程池线程运行
i++;
await Task.Delay(1000);
Trace.WriteLine(i);
}

2.3 异常处理

使用async await时,自然要处理异常。一旦异步方法抛出异常,该异常会放在返回的 Task 对象中,并且这个 Task对象的状态变为“已完成”。

当 await 调用该 Task 对象时,await 会获得并(重新)抛出该异常,并且保留着原始的栈轨迹。

async Task MethodAsync()
{
//发生异常时,任务结束,不会直接抛出异常。
var task = ThrowExceptionAsync();
try
{
//await时,抛出异常
await task;
}
catch (Exception ex)
{
Trace.WriteLine(ex.StackTrace);
}
} async Task ThrowExceptionAsync()
{
await Task.Delay(1000);
throw new Exception("ThrowExceptionAsync");
}

2.4 一旦在代码中使用了异步,最好一直使用

关于异步方法,还有一条重要的准则:一旦在代码中使用了异步,最好一直使用。调用异步方法时,应该(在调用结束时)用 await 等待它返回的 task 对象。

一定要避免使用Task.Wait 或 Task<T>.Result 方法,因为它们会导致死锁。参考一下下面这个方法:

void Deadlock()
{
// 开始延迟
Task task = WaitAsync();
// 同步程序块,正在等待异步方法完成
task.Wait();
}
async Task WaitAsync()
{
// 这里 awati 会捕获当前上下文
await Task.Delay(TimeSpan.FromSeconds(1));
// 这里会试图用上面捕获的上下文继续执行
}

如果从 UI 或 ASP.NET 的上下文调用这段代码,就会发生死锁。这是因为,这两种上下文每次只能运行一个线程。Deadlock 方法调用 WaitAsync 方法,WaitAsync 方法开始调用delay 语句。

然后,Deadlock 方法(同步)等待 WaitAsync 方法完成,同时阻塞了上下文线程。当 delay 语句结束时,await 试图在已捕获的上下文中继续运行 WaitAsync 方法,但这个步骤无法成功,

因为上下文中已经有了一个阻塞的线程,并且这种上下文只允许同时运行一个线程。

这里有两个方法可以避免死锁:

在 WaitAsync 中使用 ConfigureAwait(false)(导致 await 忽略该方法的上下文),

或者用 await 语句调用 WaitAsync 方法(让 Deadlock变成一个异步方法)。

三 并行编程简介

如果程序中有大量的计算任务,并且这些任务能分割成几个互相独立的任务块,可以考虑使用并行编程。并行编程可临时提高 CPU 利用率,以提高吞吐量。

并行的形式有两种:

  • 数据并行(data parallelism):有大量的数据需要处理,并且每一块数据的处理过程基本上是彼此独立的。

  • 任务并行(task parallelim):需要执行大量任务,并且每个任务的执行过程基本上是彼此独立的。任务并行可以是动态的,如果一个任务的执行结果会产生额外的任务,这些新增的任务也可以加入任务池。

3.1 数据并行

实现数据并行有几种不同的做法。

一种做法是使用 Parallel.ForEach 方法,它类似于foreach 循环,应尽可能使用这种做法。

Parallel 类也提供 Parallel.For 方法,这类似于 for 循环,当数据处理过程基于一个索引时,可使用这个方法。

另一种做法是使用 PLINQ(Parallel LINQ), 它为 LINQ 查询提供了 AsParallel 扩展。

与PLINQ 相比,Parallel 对资源更加友好,Parallel 与系统中的其他进程配合得比较好 , 而PLINQ 会试图让所有的 CPU 来执行本进程。

//Parallel.ForEach
void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
} //PLINQ
IEnumerable<bool> PrimalityTest(IEnumerable<int> values)
{
return values.AsParallel().Select(val => IsPrime(val));
}

不管选用哪种方法,在并行处理时有一个非常重要的准则。只要任务块是互相独立的,并行性就能做到最大化。

一旦在多个线程中共享状态,就必须以同步方式访问这些状态,那样程序的并行性就变差了。

有多种方式可以控制并行处理的输出。可以把结果存在某些并发集合,或者对结果进行聚合。

3.2 任务并行

任务并行关注执行任务,Parallel 类的 Parallel.Invoke 方法可以以 分叉 / 联合”(fork/join)的方式的使任务并行。

调用该方法时,把要并行执行的委托(delegate)作为传入参数:

void ProcessArray(double[] array)
{
Parallel.Invoke(
() => ProcessPartialArray(array, 0, array.Length / 2),
() => ProcessPartialArray(array, array.Length / 2, array.Length)
);
}
void ProcessPartialArray(double[] array, int begin, int end)
{
// CPU 密集型的操作……
}

任务并行也强调任务块的独立性。委托(delegate)的独立性越强,程序的执行效率就越高。

四 并发编程的集合

并发编程所用到的集合有两类:

  • 并发集合;
  • 不可变集合。

4.1 并发集合

多个线程可以用安全的方式同时更新并发集合。大多数并发集合使用快照snapshot),

当一个线程在增加或删除数据时,另一个线程也能枚举数据。比起给常规集合加锁以保护数据的方式,采用并发集合的方式要高效得多。

4.2 不可变集合

不可变集合实际上是无法修改的。要修改一个不可变集合,需要建立一个新的集合来代表这个被修改了的集合。

C#并发编程-1 并发编程概述的更多相关文章

  1. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  2. geotrellis使用(六)Scala并发(并行)编程

    本文主要讲解Scala的并发(并行)编程,那么为什么题目概称geotrellis使用(六)呢,主要因为本系列讲解如何使用Geotrellis,具体前几篇博文已经介绍过了.我觉得干任何一件事情基础很重要 ...

  3. Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    Java并发编程:并发容器之CopyOnWriteArrayList(转载) 原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW ...

  4. Java并发编程:并发容器之ConcurrentHashMap(转载)

    Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...

  5. Java并发编程:并发容器之ConcurrentHashMap

    转载: Java并发编程:并发容器之ConcurrentHashMap JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的 ...

  6. Java并发编程:并发容器之CopyOnWriteArrayList

    转载: Java并发编程:并发容器之CopyOnWriteArrayList Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个 ...

  7. Java并发编程:并发容器ConcurrentHashMap

    Java并发编程:并发容器之ConcurrentHashMap(转载) 下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concu ...

  8. 【Java并发编程】并发编程大合集-值得收藏

    http://blog.csdn.net/ns_code/article/details/17539599这个博主的关于java并发编程系列很不错,值得收藏. 为了方便各位网友学习以及方便自己复习之用 ...

  9. .net 系列:并发编程之一 并发编程的初步理论

    一.关于并发编程的几个误解 1)并发就是多线程 实际上多线程只是并发编程的一种形式而已,在C#中还有很多其他的并发编程技术,包括异步编程,并行编程,TPL数据流,响应式编程等.  2)只有大型服务器才 ...

随机推荐

  1. P4289 【一本通提高篇广搜的优化技巧】[HAOI2008]移动玩具

    [HAOI2008]移动玩具 题目描述 在一个 4 × 4 4\times4 4×4 的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方 ...

  2. RabbitMQ细说之开篇

    前言 关于消息中间件的应用场景,小伙伴们应该都耳熟能详了吧,比如经常提到的削峰填谷.分布式事务.异步业务处理.大数据分析等等,分布式消息队列成为其中比较关键的桥梁,也就意味着小伙伴们得掌握相关技能:当 ...

  3. rsync 文件备份

    # rsync # 实现文件的备份. # 备份位置可以是当前主机,也可以是远程主机. # rsync实现了完全备份和增量备份 # 可以做到:1.将本地主机的文件复制到另一个位置(本地.远程). # 2 ...

  4. 网络编程、OSI七层协议

    目录 软件开发架构 1.什么是软件开发架构 2.软件开发架构 3.架构优劣势 4.架构发展趋势 网络编程简介 1.如何理解网络编程 2.网络编程的目的 3.网络编程的意义 4.网络编程的起源 5.网络 ...

  5. 羽夏看Linux内核——段相关入门知识

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  6. DolphinScheduler 集群高可用测试:有效分摊服务器压力,达到性能最大优化!

    点击上方 蓝字关注我们 1 文档编写目的 Apache DolphinScheduler(简称DS)是一个分布式去中心化,易扩展的可视化DAG工作流任务调度平台.在生产环境中需要确保调度平台的稳定可靠 ...

  7. HDU3085 Nightmare Ⅱ (双向BFS)

    联赛前该练什么?DP,树型,状压当然是爆搜啦 双向BFS就是两个普通BFS通过一拼接函数联系,多多判断啦 #include <iostream> #include <cstdio&g ...

  8. HC32L110 系列 M0 MCU 的介绍和Win10下DAP-Link, ST-Link, J-Link的烧录

    HC32L110 系列 Cortex M0 MCU Hackaday 在三月份的时候介绍了一款最小的MCU NEW PART DAY: SMALLEST ARM MCU UPROOTS COMPETI ...

  9. 【游记】CSP 2021 J2

    这次是第一次参加CSP的复赛,所以考的就很LJ. \(DAY-\infty\) 到 \(DAY-14\) 知道了自己苟过了初赛,像个SB一样. (我初赛66分,旁边那位63.5,cao着线过去的) \ ...

  10. java-异常处理和线程的一些简单方法及使用

    1.1 子类重写父类含有throws声明异常抛出的方法时的规则: 1.允许不再抛出任何异常. 2.仅抛出部分异常. 3.抛出父类方法抛出异常的子类型异常. 4.不可以抛出额外异常. 5.不能抛出父类方 ...