什么是异步程序

最近在学C#,有一个业务需要用到多线程技术;

记得上大二的时候,选修Java的时候就接触过多线程,只是觉得很深奥,很“高级”。后来在学习和工作的过程中,慢慢的接触到一些周边知识,比如:并发,并行,异步,同步,线程同步,线程安全,协程等。

在看两本书,一本《C#本质论5.0》,一本《C#图解教程》,都是老外写的书。不过我感觉《C#本质论5.0》对于初学者还是有点晦涩,其中一部分原因可能翻译的不咋地,作者对一个知识点的铺陈逻辑有点跳跃,而且很容易一下子就坠入一个技术的很细节,很难懂的部分(比如作者在讲多线程的时候,总是不时的提一下子线程异常的一些“奇技淫巧”,对于初学者会有一种盲人摸象的感觉)。《C#图解教程》就好的多,作者每个知识点都会告诉读者用“某个技术”的固有代码语法套路是什么,然后接着给一个代码示例并用图示标注的方式帮助读者理解,让读者对“某个技术”瞬间掌握一个很宏观的感性印象,之后才会将一些需要注意的细节。

以前接触过C++的Boost库(里面有关于异步操作的类),是第一次接触“异步”程序的概念。其实我用异步技术已经用的很多了,对于使用Matlab进行高密度仿真计算的用户来讲,其中的并行运算parfor工具或者集群运算工具并不陌生。并行运算是异步的一中情况。

包括我在内,很多人可能刚开始搞不清异步编程和多线程编程的区别。真理引用一句我在搜集相关资料时看到的一句话“多线程是手段,异步是目的”。异步程序的实现需要多线程技术吗??有时候需要有时候不需要。

比如有的资料举的DMA的例子(上过微机原理的应该不陌生),还有获取网页内容的例子。如果读者熟悉上位机软件的话理解起来就更容易。比如有一个工业应用:机械手从某个盒子里取出零件放到另一个盒子里。假如每次开机初始化时,我们需要上位机(计算机)软件需要向机械手发一个指令:请先回到你的原点位置。上位机发送完这个指令后它可以有两种反应:

(1)一直等着机械手回到它的原点位置,直到收到机械手发过来“已经回到原点”信号,否则啥也不做。这时候你的上位机软件肯定卡死,因为它在这期间“啥也不做”,包括前台UI刷新操作。想想都觉的傻X,是不是——请不要这样编程。

(2)发出指令后,上位机软件就不管“机械手回原点”这档子事了,它会继续做别的事情,比如初始化用户界面,加载一些本地配置文件等等,总之在“机械手回原点”这段时间上位机可以做很多事情。等“机械手回到原点”时,机械手控制器给上位机发一个“机械手已经回到原点”告知信号就可以了,上位机收到该信号后再做对应的处理。这是很“符合常理”的一种编程策略。请看下面生活中的一个例子。

假如你是一个CPU,周末上午你要做以下事情:整理房间,用洗衣机洗衣服。开始时,你需要向洗衣机下达一个“开始洗衣服”指令,然后洗衣机就开始按照常用的洗衣流程开始洗衣服。下达完这个指令后,你可以有两种后续反应:

(1)搬把椅子坐在洗衣机旁边,直到等到它洗完,否则啥也不干。这对应于上面上位机例子的第一种反应——所以你觉察出这种处理策略很傻X了吧。

(2)按下洗衣机洗衣按钮后,你就不再管洗衣机怎么洗衣服这件事情了,而是去整理房间——这是一种很“符合常理“的时间规划策略。等我听到洗衣机洗完衣服发出”滴~,滴~“的声音,我再去基于该事件信号做对应处理(比如晾衣服)

另一个例子是向远程服务器请求网站内容。主线程主负责下达请求网页内容的命令,但是网页内容怎么反馈回来主线程并不干涉也无法干涉(这是远程服务器和网卡的任务)。

这就是异步操作——“Fire and Forget”(发后不理)——向另一个相关处理器设备下达指令然后就“不管它”。这里的“相关处理器”可以是某个硬件设备,比如相机,电机,机械手,远程服务器,也可以是DSP,或者FPGA处理器,也可以是另一个CPU,也可以是另一个线程。它们的共同点是:这些“相关处理器”能够独立完成交给他们的任务,而不需要主线程协助。这时候主线程只是一个甩手掌柜。

对于一些计算机的外围设备,可以通过“中断”机制来反应异步操作的“完成”信号,这时候不需要在CPU上主线程外再开辟新线程来“甩手”执行异步操作。比如机械手或者洗衣机的例子,其异步操作过程不需要主线程干涉。但是并不是所有情况都是这样的,在《C#图解教程》一书“异步编程”章节的第一个例子,作者就说“我们没有创建任何线程”(就实现了同时执行异步操作,或者在主线程外同时执行了另一个函数),但作者在讲解await表达式的时候,就讲了其实质:其实是在“不同的线程上执行了你的异步方法”,也就是底层其实用了有别于主线程外的线程上运行了你的方法。我觉得可能是线程池中的线程,也可能不异步方法指派给另一个CPU(如果你的计算机上是双核或多核的)。即异步操作有时候需要多线程技术有时候不需要(暂且将硬件“中断”机制当成不需要多线程技术)。这时候就可以理解“多线程技术是实现异步操作的一种手段”,但绝对不能说多线程技就是异步技术。

对于异步技术,总之主线程只发起调用异步方法,然后控制流立即返回主线程。这有别于“同步编程”,大多数我们接触的都是“同步操作”:把A事情做完后,你才能做B事情(或先做完B再做A)。比如整理房间,手洗衣服,吃饭这三件事情。你只能一件一件的做,而不能同时做,而不能甩手给任何家用电器(假如你的洗衣机坏了),在做完一件事情之前,下一件事情只能搁置。这便是同步编程。请注意这里的同步概念有别于“线程同步”中的“同步”概念,不要搞混了。

“线程同步”中的“同步”是为了保证“线程安全”采取的应对策略。当多个线程对同一个资源(变量或者代码)进行读写操作时要防止竞态情况出现,保证对共同资源的操作结果都是正确的,这就是线程安全,保证线程安全的策略就是线程间的“同步”。

在上面讲到Matlab的parfor的例子是一个异步编程的例子。这很好理解,各个for循环的单次操作都可以单独运算互不干涉,而且不需要主线程干涉和协助。Matlab的parfor异步操作是把各个单次操作分派到不同的CPU核上了,也就是并行运算。假如你电脑上有4个CPU,你parpool里面开8个worker,不会比你分派4个工作者快,甚至可能还会慢,因为这里面除并行处理外,还有并发处理(即让单个CPU开两个线程,切换着计算,实际情况也可能某个CPU分派3个计算线程),多个线程之间的上下文切换是需要代价的。还是洗衣服的例子:你有一堆衣服要洗,洗衣房有4个洗衣机,你可以把衣服分一分,让4个洗衣机同时洗,会用一台洗衣机1/4的时间就能洗完。你想更快在现有资源下是不可能做到的,即时洗衣店告诉你它们有8台洗衣机同时在帮你 洗——一种符合并行处理逻辑的情况是,每台洗衣机都有凉皮衣服要洗,每台洗衣机在机洗完第一批衣服后,不是接着漂洗、脱水而是取出第一批衣服,机洗第二批衣服,然后再取出第二批衣服,再漂洗第一批衣服,再漂洗第二批衣服,等等,感觉很麻烦,但其实CPU的并行处理就是这样做的,所谓的上下文切换代价就是指这种两批衣服“一批取出来另一批放进去,再取出来再放进去的冗余过程”,只是CPU的时间反应速度更快(ms级甚至更快),让你觉察不到他的切换,给你营造一种好像真的是一台洗衣机在同时洗两批衣服的“假象”。一台洗衣机洗两批衣服的过程是“同步”的——漂洗完一批,下一批才能开始。但洗衣机与洗衣机之间的操作是异步的,是“同时”的。

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(编程方法学)