温故之.NET 任务并行

这篇文章主要讲解 .NET 的任务并行,与数据并行不同的是:数据并行以数据为处理单元,而任务并行,则以任务(工作)为单元

任务并行基础

如果我们想要创建并行的任务,可以通过 Parallel.Invoke 来实现。它可以很方便的帮助我们同时运行多个任务,如下

public static void WorkOne() {// 任务一
}
public static void WorkTwo() {// 任务二
}
Parallel.Invoke(WorkOne, WorkTwo);

// 我们也可以通过 Lambda 表达式这样写
Parallel.Invoke(() => {// 任务一}, () => {// 任务二}
); 

借助 Parallel.Invoke ,我们只需表达想同时运行的操作,CLR 会处理所有线程调度的具体信息(包括将线程数量自动缩放至计算机上的内核数)

需要特别注意> TPL 在后台创建的 Task 数量不一定与所提供的操作的数量相等。 因为 TPL 可能会针对操作的数量进行不同程度的优化

因此,对 Parallel.Invoke ,我们可以这样理解(只是为了理解方便,不表示其内部具体实现也是这样的)

  • 分配一个具有 4 个线程的“线程池”(假设计算机处理器为 4 核 4 线程)。或者根据指定的 ParallelOptions 中的 MaxDegreeOfParallelism 属性来确定具体数量
  • 采用 Task.Run 的方式运行每一个任务。每执行一个任务,就从“线程池”中取一个空闲的线程。如果没有多余的空闲线程,则等待
  • 直到处理完所有的任务为止

这也可以理解为对其内部实现的一个猜测。如果有兴趣,可以使用 .NET Refactor 看一下其源码

如果程序有 UI 线程,且任务的创建从 UI 线程开始,那么在使用方式上会有变化,如下代码所示

Task.Run(() => {Parallel.Invoke(() => {// 任务一}, () => {// 任务二});
}); 

这对于其他的并行(如数据并行)也是一样的。只要我们需要从 UI 线程创建并行,就应该使用 Task.Run 来启动它们。否则,很有可能产生死锁(一般出现在当并行代码内部需要访问 UI 的情况下,其他情况我也暂时没有遇到过)

如果我们分不清当前创建并行的是 UI 线程还是其他类型的线程。我们可以统一使用 Task 的方式来启动它们。反正在大部分情况下,使用 Task 来启动也不会造成什么性能问题

不过,如果我们需要并行立即启动,或者尽快启动,使用 Task 来启动可能就不太合适,在系统工作量比较重的情况下,我们也不清楚这个 Task 什么时候能够执行。在这种场景下,我们可以新建一个 Thread 来做这件事。因为 ThreadTask 不同,Thread 不以任务为单位,当我们调用 Thread.Start() 的时候,线程就会立即执行。而 Task,当我们调用 Task.Run 的时候,它需要接受 TPL 的调度(Task Scheduler)。因此,其执行时间就不确定了

针对创建并行,有以下建议

  • 在不确定创建并行的是 UI 线程还是其他线程时,使用 Task.Run 来启动并行(如前面例子所示)
  • 在系统工作比较重的情况下,如果希望并行能够立即启动,我们应该使用 Thread 的方式
  • 否则,在大多数情况下,无论 PC 端、Web 端、还是 WebApi 后台,我们使用 Task.Run 来启动并行是比较好的方式

通过 Thread 方式启动并行,示例如下

Thread thread = new Thread(() => {Parallel.Invoke(() => {Debug.WriteLine("Work 1");},() => {Debug.WriteLine("Work 2");});
});
thread.Start(); 

针对并行的建议

前面提到,在多处理器条件下,使用 Parallel 可以显著提升性能。但事物总有两面性,因此还是有一些坑需要我们注意

  • 对于任务并行,如果任务间具有强关联性(即有很多任务的执行依赖于其他的任务或者多个任务之间存在资源共享)。个人不建议使用并行库,因为在以往的经验中,这样的处理并没有为我们带来特别大的性能提升
  • Parallel.ForParallel.ForEach 以数据并行为主;Parallel.Invoke 以任务并行为主
  • 不要对循环进行过度并行化。所谓物极必反,过度的并行化,不但增加了管理的难度,线程间的同步以及最后各个分区的合并,都会对性能造成影响
  • 如果并行里面的单次迭代的工作量较小,推荐使用 Partitioner 来手动的对源集合进行分块
  • 避免在并行代码块内调用非线程安全的方法,就算是声明为线程安全的方法,也应该尽量少的调用
  • 尽量避免在 UI 线程上执行并行循环。也应尽量避免在并行代码中更新 UI,因为这有可能会产生数据损坏或死锁
  • 在并行迭代中,我们不应该假定每一个迭代顺序开始。比如有集合 [1,2,3,4,5,6,7,8],假设分为 4 个分块 [1,2]、[3,4]、[5,6]、[7,8],我们不应该认为 [1,2] 这个块要比 [5,6] 这个块先执行。理解这个很重要,可以防止我们写出可能产生死锁的代码,示例如【示例A】所示

示例A

ManualResetEventSlim mre = new ManualResetEventSlim();
int processor = Environment.ProcessorCount;
var source = Enumerable.Range(0, processor * 100);
Parallel.ForEach(source, item => {if (item == processor) {mre.Set();} else {mre.Wait();}
}); 

对于这段代码,就可能会(可能性非常大)发生死锁。如前面【针对并行的建议】的最后一点所说,同样地,此处我们也无法确定 mre.Set()mre.Wait() 到底谁先执行至此,这篇文章的内容讲解完毕。

后话最近看了一些书籍,决定无论何时,凡是关注了我的朋友,都一律关注回去源于以下一点:尊重是相互的,学习也是相互的

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

​ 上面介绍了技术分类和学习路线,这里来谈一下学习方法:
​ ## 视频学习

​ 无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1 University students can understand innovation through learning from the past. 2. Students can better review by breaking down complex concepts into smaller components and studying the material in an organized way. 3. When learning from the past to understand innovation, it is important to focus on understanding the big picture and to not get bogged down in the details. ### 回答2: 1. 大学生如何理解故而知新? 故而知新是一种学习方法,它要求我们在学习新知识之前先回顾和巩固已经学过的知识。大学生理解故而知新意味着要在学习新知识之前,先回顾和复习以前学过的相关知识或基础知识。通过故,我们能够加深对已有知识的理解和记忆,从而更好地理解和掌握新的知识。 2. 学生如何更好地去复习? 学生要更好地复习,可以采取以下策略: 首先,制定一个合理的复习计划,将要复习的内容分配到不同的时间段,确保每个科目都有足够的时间。 其次,采用多种复习方法,如阅读教材、做练习题、参加讨论等,以帮助加深理解和牢固记忆。 另外,与同学或老师一起讨论复习内容,通过讲解和互动来加深理解。 此外,保持良好的学习习惯,比如及时复习、做好笔记等,能够帮助学生更好地掌握和复习知识。 3. 故而知新的过程需要注意什么? 在故而知新的过程中,需要注意以下几点: 首先,要有针对性,根据自己的学习需求和复习目标,选择性地回顾和复习相关知识点。 其次,要有系统性,将复习内容进行分类整理,形成一个清晰的知识框架,有助于加深理解和记忆。 另外,要关注重难点,重点复习那些相对较难或容易遗忘的知识点,加强对这些内容的学习和理解。 还要有耐心和恒心,故而知新是一个持续的过程,需要长期坚持和不断巩固。 最后,要善于总结和归纳,通过整理和回顾复习过程中的笔记和练习,提炼出关键概念和思维模式,便于记忆和应用。 ### 回答3: 1. 大学生如何理解故而知新? 大学生可以理解为通过回顾过去的知识和经验,来获取新的见解和理解。故是指回顾已经学过的知识,了解其中的原理、概念和重要点。而知新则是指通过对新知识的学习,扩展和更新自己的知识体系。故而知新相辅相成,是一个持续学习和发展的过程。 2. 学生如何更好地去复习? 学生可以通过以下方式更好地进行复习: - 制定合理的复习计划:根据时间安排和课程难度,合理分配复习时间,确保每个学科都有足够的复习时间。 - 多种复习方法结合:采用不同的学习方式,如阅读教材、做练习题、参与讨论、制作思维导图等,帮助巩固记忆和理解知识。 - 主动参与课堂:积极参与讨论和提问,与同学和老师交流,加深对知识的理解和记忆。 - 不断反思和总结:及时检查自己的复习情况,发现不足和问题,并及时调整学习方法和计划。 3. 故而知新的过程需要注意什么? 在故而知新的过程中,学生需要注意以下几点: - 有目的性地故:针对具体的知识点或者问题进行回顾,明确自己的学习目标和重点。 - 理解和记忆结合:不仅要理解概念和原理,还要通过多次的复习和记忆,帮助信息在大脑中形成长期记忆。 - 理论联系实际:将学到的知识应用到实际情境中,加深对知识的理解和记忆。 - 及时巩固复习成果:通过做练习题、整理笔记、与同学讨论等方式,巩固复习的成果,确保知识掌握得更牢固。 - 长期持续学习:故而知新是一个持续的过程,要保持学习的热情和动力,不断更新自己的知识体系。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值