《C#并发编程经典实例》学习笔记-第一章并发编程概述
并发编程的术语
- 并发
同时做多件事情 - 多线程
并发的一种形式,它采用多个线程来执行程序。
多线程是并发的一种形式,但不是唯一的形式。 - 并行处理
把正在执行的大量的任务分割成小块,分配给多个同时运行的线程。
并行处理是多线程的一种,而多线程是并发的一种。 - 异步编程
并发的一种形式,它采用future模式或回调(callback)机制,以避免产生不必要的线程。
一个 future(或 promise)类型代表一些即将完成的操作。在 .net 中,新版 future 类型有 task 和 task。在老式异步编程 api 中,采用回调或事件(event),而不是future。异步编程的核心理念是异步操作(asynchronous operation):启动了的操作将会在一段时间后完成。这个操作正在执行时,不会阻塞原来的线程。启动了这个操作的线程,可以继续执行其他任务。当操作完成时,会通知它的 future,或者调用回调函数,以便让程序知道操作已经结束。 - 响应式编程
一种声明式的编程模式,程序在该模式中对事件做出响应。
响应式编程的核心理念是异步事件(asynchronous event):异步事件可以没有一个实际的“开始”,可以在任何时间发生,并且可以发生多次,例如用户输入。
如果把一个程序看作一个大型的状态机,则该程序的行为便可视为它对一系列事件做出响应,即每换一个事件,它就更新一次自己的状态。
异步编程的两个好处
- 对于面向终端用户的 gui 程序:异步编程提高了响应能力。面对在运行时被临时锁定界面的程序,异步编程可以使程序在此时仍能流畅的响应用户的输入。譬如:wpf界面,执行一个需要等待的操作时,仍可以点击输入框进行填写,而不会出现卡顿,无法点击的情况或者对页面无法进行拖拽。
- 对于服务器端应用:异步编程实现了可扩展性。服务器应用可以利用线程池满足其可扩展性,使用异步编程后,可扩展性通常可以提高一个数量级。即提高服务器端应用的tps(transactions per second)和 qps (queries per second)
并行的两种形式
并行编程的使用场景:需要执行大量的计算任务,并且这些任务能分割成相互独立的任务块儿
并行的形式有两种:数据并行(data parallelism)和任务并行(task parallelim)。
数据并行(data parallelism):有大量的数据需要处理,并且每一块数据的处理过程基本上是彼此独立的。
任务并行(task parallelim):需要执行大量任务,并且每个任务的执行过程基本上是彼此独立的。任务并行可以是动态的,如果一个任务的执行结果会产生额外的任务,这些新增的任务也可以加入任务池。
实现数据并行的方法
- parallel.foreach
- plinq(parallel linq)
每个任务块要尽可能的互相独立。 只要任务块是互相独立的,并行性就能做到最大化。一旦你在多个线程*享状态,就必须以同步方式访问这些状态,那样程序的并行性就变差了。
数据并行重点在处理数据,任务并行则关注执行任务。
实现任务并行的方法
- parallel.invoke
- task.wait
通常情况下,没必要关心线程池处理任务的具体做法。数据并行和任务并行都使用动态调整的分割器,把任务分割后分配给工作线程。线程池在需要的时候会增加线程数量。线程池线程使用工作窃取队列(work-stealing queue)。
响应式编程rx学习难度较大
使用场景:处理的事件中带有参数,最好采用响应式编程
响应式编程的核心概念是:可观察的流(observable stream)
响应式编程的最终代码非常像 linq,可以认为它就是“linq to events”,它采用“推送”模式,事件到达后就自行穿过查询。
tpl数据流
异步编程和并行编程这两种技术结合起来就是tpl数据流
数据流网格的基本组成单元是数据流块(dataflow block)。
rx 和 tpl有很多相同点。
网格和流都有“数据项”这一概念,数据项从网格或流的中间穿过。还有,网格和流都有“正常完成”(表示没有更多数据需要接收时发出的通知)和“不正常完成”(在处理数据中发生错误时发出的通知)这两个概念。但是,rx 和 tpl 数据流的性能并不相同。
当需要执行需要计时的任务,最佳选择是rx的 可观察流 observable 对象
当需要进行并行处理,最佳选择是 tpl数据流块
线程和线程池
线程是一个独立的运行单元,每个进程内部有多个线程,每个线程可以各自同时执行指令。每个线程有自己独立的栈,但是与进程内的其他线程共享内存。
对某些程序来说,其中有一个线程是特殊的,例如用户界面程序有一个 ui 线程,控制台程序有一个 main 线程。
每个 .net 程序都有一个线程池,线程池维护着一定数量的工作线程,这些线程等待着执行分配下来的任务。线程池可以随时监测线程的数量。配置线程池的参数多达几十个,但是建议采用默认设置,线程池的默认设置是经过仔细调整的,适用于绝大多数现实中的应用场景。
并发编程的设计原理
大多数并发编程技术有一个类似点:它们本质上都是函数式(functional)的。函数式编程理念是并发编程的本质。
推荐阅读
-
《C#并发编程经典实例》学习笔记—3.1 数据的并行处理
-
《C#并发编程经典实例》学习笔记—2.1 暂停一段时间
-
《C#并发编程经典实例》读书笔记-关于并发编程的几个误解
-
《C#并发编程经典实例》学习笔记—2.2 返回完成的任务
-
《C#并发编程经典实例》学习笔记-进程(process)和线程(thread)
-
《C#并发编程经典实例》学习笔记—2.9 处理 async void 方法的异常
-
《C#并发编程经典实例》学习笔记—2.8 处理 async Task 方法的异常
-
《C#并发编程经典实例》学习笔记-第一章并发编程概述
-
《C#并发编程经典实例》学习笔记-第一章并发编程概述
-
《C#并发编程经典实例》学习笔记—2.5 等待任意一个任务完成 Task.WhenAny