简介
随着互联网的飞速发展,服务器端的性能要求也越来越高。其中,每秒查询率(QPS)是一个重要的衡量指标,它反映了服务器每秒处理请求的能力。异步编程模式是一种高效的编程模式,可以有效提升服务器的 QPS。
同步编程模式
在同步编程模式中,当一个请求到来时,服务器会立即执行该请求,并等待请求执行完毕后才返回响应。这种模式存在的主要问题是,当请求较多时,服务器可能会因为等待而无法及时处理新的请求,导致响应时间变长,QPS 降低。
异步编程模式
与同步编程模式不同,异步编程模式允许服务器在处理完一个请求后立即返回响应,而无需等待请求执行完毕。这样,服务器就可以同时处理多个请求,从而提高 QPS。异步编程模式的主要思想是使用回调函数或事件监听器。当一个请求到来时,服务器会将请求交给一个回调函数或事件监听器,然后继续处理其他请求。当请求执行完毕后,回调函数或事件监听器会被调用,并执行相应的处理逻辑。
异步编程模式的优势
异步编程模式相比同步编程模式具有以下优势:高并发能力:异步编程模式允许服务器同时处理多个请求,从而提高并发能力。低延迟:异步编程模式可以减少请求的等待时间,从而降低延迟。高吞吐量:异步编程模式可以提高服务器的吞吐量,即每秒处理的请求数。
异步编程模式的实现方式
异步编程模式可以采用多种方式实现,常见的实现方式包括:回调函数:回调函数是一种函数,它会在某个事件发生后被调用。在异步编程中,当请求执行完毕后,服务器会调用回调函数,并传入请求的响应结果。事件监听器:事件监听器是一种监听特定事件的对象。在异步编程中,当某个事件发生时,事件监听器会被触发,并执行相应的
JavaScript异步编程(js的异步编程)
下面哪些方法可以用作javascript异步模式的编程
javascript语言是单线程机制。
所谓单线程就是按次序执行,执行完一个任务再执行下一个。
对于浏览器来说,也就是皮芦无法在渲染页面的同时执行代码。
单线程机制的优点在于实现起来较为简单,运行环境相对简单。缺点在于,如果中间有任务需要响应时间过长,经常会导致
页面加载错误或者浏览器无响应的状况。
这就是所谓的逗同步模式地,程序执行顺序与任务排列顺序一致。
对于浏览器来说,
同步模式效率较低,耗时长的任务都应该使用异步模式;而在服务器端,异步模式则是唯一的模式,如果采用同步模式个人认为
服务器很快就会出现在高峰期的表现。
。
。
。
异步模式的四种方式:
1.回调函数callback
所谓回调函数,就是将函数作为参数传到需要回调的函数内部再执行。
典型的例子就是发送ajax请求。例如:
async:false,
cache:false,
dataType:json,
success:function(data){
(success);
error:function(data){
当发送ajax请求后,等待回应的过程不会堵塞程序运行,耗时燃姿带的操作相当于延后执行。
回调函数的优点在于简单,容易理解,但是可读性较差,耦合度较高,不易于维护。
2.事件驱动
javascript可以称之为是基于对象的语言,而基于对象的基本特征册尘就是事件驱动(Event-Driven)。
事件驱动,指的是由鼠标和热键的动作引发的一连串的程序操作。
例如,为页面上的某个
$(#btn)(function(){
(clickbutton);
绑定事件相当于在元素上进行监听,是否执行注册的事件代码取决于事件是否发生。
优点在于容易理解,一个元素上可以绑定多个事件,有利于实现模块化;但是缺点在于称为事件驱动的模型后,流程不清晰。
3.发布/订阅
发布订阅模式(publish-subscribepattern)又称为观察者模式(Observerpattern)。
该模式中,有两类对象:观察者和目标对象。目标对象中存在着一份观察者的列表,当目标对象
的状态发生改变时,主动通知观察者,从而建立一种发布/订阅的关系。
jquery有相关的插件,在这不是重点不细说了。
。
。
。
回头写个实现贴上来
模式
promise对象是CommonJS工作组提供的一种规范,用于异步编程的统一接口。
promise对象通常实现一种then的方法,用来在注册状态发生改变时作为对应的回调函数。
promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已完成(resolved)和拒绝(rejected)。以CommonJS
标准为例,promise对象上的then方法负责添加针对已完成和拒绝状态下的处理函数。then方法会返回另一个promise对象,以便于形成promise管道,这种返回promise对象的方式能够支持开发人员把异步操作串联起来,如then(resolvedHandler,
rejectedHandler);。resolvedHandler
回调函数在promise对象进入完成状态时会触发,并传递结果;rejectedHandler函数会在拒绝状态下调用。
Jquery在1.5的版本中引入了一个新的概念叫Deferred,就是CommonJSpromiseA标准的一种衍生。可以在jQuery中创建
$的对象。
同时也对发送ajax请求以及数据类型有了新的修改,参考JQueryAPI。
除了以上四种,javascript中还可以利用各种函数模拟异步方式,更有诡异的诸如用同步调用异步的case
只能用team里同事形容java和javascript的一句话作为结尾:
逗写java像在高速路上开车,写javascript像在草原上开车地————-以此来形容javascript这种无类型的语言有多自由
but,如果草原上都是坑。
千锋web前端培训告诉你学哪些内容
千锋教育专业的前端培训机构,教学课程完善,web前端课程分采用进阶式学习,阶段性检测学员掌握学员学习情况。
课程体系能够适应市场需求、紧跟时代技术,完全满足市场对前端工程师的要求,大大地提升了学员的市场竞争力。
web前端学习内容包括7大学习阶段:
阶段一:前端页面重构
阶段二:JavaScript高级程序设计
阶段三:PC端全栈项目开发
阶段四:移动端项目开发
阶段五:混合(Hybrid,ReactNative)开发
阶段六:NodeJS全栈开发
阶段七:大数据可视化码孝好
千锋教育前端课程顺应市场的慎指需求,不断编订更新,让学员学到的都是当下企业急需的技术,如果对千锋的课程感兴趣,可以来千锋免费试听课程,为学员免费提供了长达两周的课程试听,让先了解后再决定要不要系统地学习,只有知道是不是适合自己,才能做出正确的决定。
想要了解更多有关Web前端的相关信息,推荐咨询千锋教育。
千锋教育成立教研学科中心,推出贴近企业需求的线下技能培训课程。
课程包含HTML5大前端培训、JavaEE+分布式开发培训、Python人工智能+数据分析培训、全链路UI/UE设计培训、云计算培训、全栈软件测试培训、大数据+人工智能培训、智能物联网+嵌入迟铅式培训、Unity游戏开发培训、网络安全培训、区块链培训、影视剪辑包装培训、游戏原画培训、全媒体运营培训。
采用全程面授高品质、高体验培养模式,非常值得选择。
javascript中异步操作的异常怎么处理
一、JavaScript异步编程的两个核心难点
异步I/O、事件驱动使得单线程的JavaScript得以在不阻塞UI的情况下执行网络、文件访问功能,且使之在后端实现了较高的性能。然而异步风格也引来了一些麻烦,其中比较核心的问题是:
1、函数嵌套过深
JavaScript的异步调用基于回调函数,当多个异步事务多级依赖时,回调函数会形成多级的嵌套,闷枯代码变成
金字塔型结构。
这不仅使得代码变难看难懂,更使得调试、重构的过程充满风险。
2、异常处理
回调嵌套不仅仅是使代码变得杂乱,也使得错误处理更复杂。
这里主要讲讲异常处理。
二、异常处理
像很多时髦的语言一样,JavaScript也允许抛出异常,随后再用一个try/catch
语句块捕获。
如果抛出的异常未被捕获,大多数JavaScript环境都会提供一个有用的堆栈轨迹。
举个例子,下面这段代码由于{为无效JSON
对象而抛出异常。
functionJSONToObject(jsonStr){(jsonStr);}varobj=JSONToObject({);//SyntaxError:Unexpectedendofinput//(native)//atJSONToObject(/AsyncJS/:2:15)//答罩旅(/AsyncJS/:4:11)
堆栈轨迹不仅告诉我们哪里抛出了错误,而且说明清凳了最初出错的地方:第4行代码。
遗憾的是,自顶向下地跟踪异步错误起源并不都这么直截了当。
异步编程中可能抛出错误的情况有两种:回调函数错误、异步函数错误。
1、回调函数错误
如果从异步回调中抛出错误,会发生什么事?让我们先来做个测试。
setTimeout(functionA(){setTimeout(functionB(){setTimeout(functionC(){thrownewError(Somethingterriblehashappened!);},0);},0);},0);
上述应用的结果是一条极其简短的堆栈轨迹。
Error:Somethingterriblehashappened!atTimer.C(/AsyncJS/:4:13)
等等,A和B发生了什么事?为什么它们没有出现在堆栈轨迹中?这是因为运行C的时候,异步函数的上下文已经不存在了,A和B并不在内存堆栈里。这3
个函数都是从事件队列直接运行的。基于同样的理由,利用try/catch
语句块并不能捕获从异步回调中抛出的错误。
另外回调函数中的return也失去了意义。
try{setTimeout(function(){thrownewError(Catchmeifyoucan!);},0);}catch(e){(e);}
看到这里的问题了吗?这里的try/catch语句块只捕获setTimeout函数自身内部发生的那些错误。因为setTimeout
异步地运行其回调,所以即使延时设置为0,回调抛出的错误也会直接流向应用程序。
总的来说,取用异步回调的函数即使包装上try/catch语句块,也只是无用之举。
(特例是,该异步函数确实是在同步地做某些事且容易出错。
例如,Node
的(file,callback)就是这样一个函数,它在目标文件不存在时会抛出一个错误。)正因为此,
中的回调几乎总是接受一个错误作为其首个参数,这样就允许回调自己来决定如何处理这个错误。
2、异步函数错误
由于异步函数是立刻返回的,异步事务中发生的错误是无法通过try-catch来捕捉的,只能采用由调用方提供错误处理回调的方案来解决。
例如Node中常见的function(err,…)
{…}回调函数,就是Node中处理错误的约定:即将错误作为回调函数的第一个实参返回。
再比如HTML5中FileReader对象的onerror函数,会被用于处理异步读取文件过程中的错误。
举个例子,下面这个Node应用尝试异步地读取一个文件,还负责记录下任何错误(如“文件不存在”)。
varfs=require(fs);(,function(err,data){if(err){(err);};((utf8));});
客户端JavaScript库的一致性要稍微差些,不过最常见的模式是,针对成败这两种情形各规定一个单独的回调。jQuery的Ajax
方法就遵循了这个模式。
$(/data,{success:successHandler,failure:failureHandler});
不管API形态像什么,始终要记住的是,只能在回调内部处理源于回调的异步错误。
三、未捕获异常的处理
如果是从回调中抛出异常的,则由那个调用了回调的人负责捕获该异常。但如果异常从未被捕获,又会怎么样?这时,不同的JavaScript环境有着不同的游戏规则……
1.在浏览器环境中
现代浏览器会在开发人员控制台显示那些未捕获的异常,接着返回事件队列。要想修改这种行为,可以给
附加一个处理器。
如果处理器返回true,则能阻止浏览器的默认错误处理行为。
=function(err){returntrue;//彻底忽略所有错误};
在成品应用中,会考虑某种JavaScript错误处理服务,譬如Errorception。Errorception
提供了一个现成的处理器,它向应用服务器报告所有未捕获的异常,接着应用服务器发送消息通知我们。
2.在环境中
在Node环境中,的类似物就是process对象的uncaughtException事件。正常情况下,Node
应用会因未捕获的异常而立即退出。但只要至少还有一个uncaughtException事件处理
器,Node应用就会直接返回事件队列。
(uncaughtException,function(err){(err);//避免了关停的命运!});
但是,自Node0.8.4起,uncaughtException事件就被废弃了。据其文档所言,对异常处理而言,uncaughtException
是一种非常粗暴的机制,请勿使用uncaughtException,而应使用Domain对象。
Domain对象又是什么?你可能会这样问。
Domain对象是事件化对象,它将throw转化为error事件。
下面是一个例子。
varmyDomain=require(domain)();(function(){setTimeout(function(){thrownewError(Listentome!)},50);});(error,function(err){(Errorignored!);});
源于延时事件的throw只是简单地触发了Domain对象的错误处理器。
Errorignored!
很奇妙,是不是?Domain对象让throw
语句生动了很多。
不管在浏览器端还是服务器端,全局的异常处理器都应被视作最后一根救命稻草。
请仅在调试时才使用它。
四、几种解决方案
下面对几种解决方案的讨论主要集中于上面提到的两个核心问题上,当然也会考虑其他方面的因素来评判其优缺点。
首先是Node中非常著名的,这个库能够在Node中展露头角,恐怕也得归功于Node统一的错误处理约定。
而在前端,一开始并没有形成这么统一的约定,因此使用的话可能需要对现有的库进行封装。
的其实就是给回调函数的几种常见使用模式加了一层包装。比如我们需要三个前后依赖的异步操作,采用纯回调函数写法如下:
asyncOpA(a,b,(err,result)={if(err){handleErrorA(err);}asyncOpB(c,result,(err,result)={if(err){handleErrorB(err);}asyncOpB(d,result,(err,result)={if(err){handlerErrorC(err);}finalOp(result);});});});
如果我们采用async库来做:
([(cb)={asyncOpA(a,b,(err,result)={cb(err,c,result);});},(c,lastResult,cb)={asyncOpB(c,lastResult,(err,result)={cb(err,d,result);})},(d,lastResult,cb)={asyncOpC(d,lastResult,(err,result)={cb(err,result);});}],(err,finalResult)={if(err){handlerError(err);}finalOp(finalResult);});
可以看到,回调函数由原来的横向发展转变为纵向发展,同时错误被统一传递到最后的处理函数中。
其原理是,将函数数组中的后一个函数包装后作为前一个函数的末参数cb传入,同时要求:
每一个函数都应当执行其cb参数;cb的第一个参数用来传递错误。我们可以自己写一个的实现:
letasync={waterfall:(methods,finalCb=_emptyFunction)={if(!_isArray(methods)){returnfinalCb(newError(Firstargumenttowaterfallmustbeanarrayoffunctions));}if(!){returnfinalCb();}functionwrap(n){if(n===){returnfinalCb;}returnfunction(err,){if(err){returnfinalCb(err);}methods[n](,wrap(n+1));}}wrap(0)(false);}};
还有series/parallel/whilst等多种流程控制方法,来实现常见的异步协作。
的问题:
在外在上依然没有摆脱回调函数,只是将其从横向发展变为纵向,还是需要程序员熟练异步回调风格。
错误处理上仍然没有利用上try-catch和throw,依赖于“回调函数的第一个参数用来传递错误”这样的一个约定。
2、Promise方案
把前面提到的功能用Promise来实现,需要先包装异步函数,使之能返回一个Promise:
functiontoPromiseStyle(fn){return()={returnnewPromise((resolve,reject)={fn(,(err,result)={if(err)reject(err);resolve(result);})});};}
这个函数可以把符合下述规则的异步函数转换为返回Promise的函数:
回调函数的第一个参数用于传递错误,第二个参数用于传递正常的结果。接着就可以进行操作了:
let[opA,opB,opC]=[asyncOpA,asyncOpB,asyncOpC]((fn)=toPromiseStyle(fn));opA(a,b)((res)={returnopB(c,res);})((res)={returnopC(d,res);})((res)={returnfinalOp(res);})((err)={handleError(err);});
通过Promise,原来明显的异步回调函数风格显得更像同步编程风格,我们只需要使用then方法将结果传递下去即可,同时return也有了相应的意义:
在每一个then的onFullfilled函数(以及onRejected)里的return,都会为下一个then的onFullfilled函数(以及onRejected)的参数设定好值。
如此一来,return、try-catch/throw都可以使用了,但catch是以方法的形式出现,还是不尽如人意。
3、Generator方案
ES6引入的Generator可以理解为可在运行中转移控制权给其他代码,并在需要的时候返回继续执行的函数。
利用Generator可以实现协程的功能。
将Generator与Promise结合,可以进一步将异步代码转化为同步风格:
function*getResult(){letres,a,b,c,d;try{res=yieldopA(a,b);res=yieldopB(c,res);res=yieldopC(d);returnres;}catch(err){returnhandleError(err);}}
然而我们还需要一个可以自动运行Generator的函数:
functionspawn(genF,){returnnewPromise((resolve,reject)={letgen=genF();functionnext(fn){try{letr=fn();if(){resolve();}()((v)={next(()={(v);});})((err)={next(()={(err);})});}catch(err){reject(err);}}next(()={(undefined);});});}
用这个函数来调用Generator即可:
spawn(getResult)(
Async&Future异步编程机制以及功能分析讲解
本文内容
Future模式介绍以及核心思想
核心线程数、最大线程数的区别,队列容量代表什么;
ThreadPoolTaskExecutor饱和策略;
SpringBoot异步编程实战,搞懂代码的执行逻辑。
Future模式
异步编程在处理耗时操作以及多任务处理的场景下非常有用,我们可以更好的让我们的系统利用好机器的CPU和内存,提高它们的利用率。
多线程设计模式有很多种,Future模式是多线程开发中较为常见的一种设计模式,本文也是基于这种模式来说明SpringBoot对于异步编程的知识。
实战之前我先简单介绍一下Future模式的核心思想吧!。
Future的核心思想
Future模式的核心思想是异步调用。
当我们执行一个方法时,假如这个方法中有多个耗时的任务需要同时去做,而且又不着急等待这个结果时可以让客户端立即返回然后,后台慢慢去计算任务。
当然你也可以选择等这些任务都执行完了,再返回给客户端。
SpringBoot异步编程实战
如果我们需要在Spring/SpringBoot实现异步编程的话,通过Spring提供的两个注解会让这件事情变的非常简单。
@EnableAsync:通过在配置类或者Main类上加@EnableAsync开启对异步方法的支持。
@Async可以作用在类上或者方法上,作用在类上代表这个类的所有方法都是异步方法。
TaskExecutor
很多人对于TaskExecutor不是太了解,所以我们花一点篇幅先介绍一下这个东西。
从名字就能看出它是任务的执行者,它领导执行着线程来处理任务,就像司令官一样,而我们的线程就好比一只只军队一样,这些军队可以异步对敌人进行打击。
Spring提供了TaskExecutor接口作为任务执行者的抽象,它和包下的Executor接口很像。
稍微不同的TaskExecutor接口用到了Java8的语法@FunctionalInterface声明这个接口是一个函数式接口。
@FunctionalInterfacepublicinterfaceTaskExecutorextendsExecutor{voidexecute(Runnablevar1);}
如果没有自定义Executor,Spring将创建一个SimpleAsyncTaskExecutor并使用它。
自定义;;;;;;@Configuration@EnableAsyncpublicclassAsyncConfigimplementsAsyncConfigurer{privatestaticfinalintCORE_POOL_SIZE=6;privatestaticfinalintMAX_POOL_SIZE=10;privatestaticfinalintQUEUE_CAPACITY=100;@BeanpublicExecutortaskExecutor(){//Spring默认配置是核心线程数大小为1,最大线程容量大小不受限制,队列容量也不受限制。ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();//核心线程数(CORE_POOL_SIZE);//最大线程数(MAX_POOL_SIZE);//队列大小(QUEUE_CAPACITY);//当最大池已满时,此策略保证不会丢失任务请求,但是可能会影响应用程序整体性能。(());(MyThreadPoolTaskExecutor-);();returnexecutor;}}ThreadPoolTaskExecutor常见概念
CorePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量。
QueueCapacity:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。
MaximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
一般情况下不会将队列大小设为_VALUE,也不会将核心线程数和最大线程数设为同样的大小,这样的话最大线程数的设置都没什么意义了,你也无法确定当前CPU和内存利用率具体情况如何。
如果队列已满并且当前同时运行的线程数达到最大线程数的时候,如果再有新任务过来会发生什么呢?Spring默认使用的是(ThreadPoolExecutor将抛出RejectedExecutionException来拒绝新来的任务,这代表你将丢失对这个任务的处理。)
对于可伸缩的应用程序,建议使用,当最大池被填满时,此策略为我们提供可伸缩队列。
ThreadPoolTaskExecutor饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量时,ThreadPoolTaskExecutor定义一些策略:
:抛出RejectedExecutionException来拒绝新任务的处理。
:调用执行自己的线程运行任务。
您不会任务请求。
但是这种策略会降低对于新任务提交速度,影响程序的整体性能。
另外,这个策略喜欢增加队列容量。
如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
:不处理新任务,直接丢弃掉。
:此策略将丢弃最早的未处理的任务请求。
编写一个异步的方法
给这个方法加上了@Async注解来告诉Spring它是一个异步的方法。
另外,这个方法的返回值(results)这代表我们需要返回结果,也就是说程序必须把任务执行完成之后再返回给用户。
请留意completableFutureTask方法中的第一行打印日志这句代码,后面分析程序中会用到,很重要!
;;;;;;;;;@ServicepublicclassAsyncService{privatestaticfinalLoggerlogger=();privateList<String>movies=newArrayList<>((ForrestGump,Titanic,SpiritedAway,TheShawshankRedemption,Zootopia,Farewell,Joker,Crawl));/**示范使用:找到特定字符/字符串开头的电影*/@AsyncpublicCompletableFuture<List<String>>completableFutureTask(Stringstart){//打印日志(()()+startthistask!);//找到特定字符/字符串开头的电影List<String>results=()(movie->(start))(());//模拟这是一个耗时的任务try{(1000L);}catch(InterruptedExceptione){();}//返回一个已经用给定值完成的新的CompletableFuture。(results);}}
测试编写的异步方法
@RestController@RequestMapping(/async)publicclassAsyncController{@AutowiredAsyncServiceasyncService;@GetMapping(/movies)publicStringcompletableFutureTask()throwsExecutionException,InterruptedException{//开始时间longstart=();//开始执行大量的异步任务List<String>words=(F,T,S,Z,J,C);List<CompletableFuture<List<String>>>completableFutureList=()(word->(word))(());//()方法可以获取他们的结果并将结果连接起来List<List<String>>results=()(CompletableFuture::join)(());//打印结果以及运行程序运行花费时间(Elapsedtime:+(()-start));();}}
请求这个接口,控制台打印出下面的内容:
2019-10-0113:50:17.007WARN—[lTaskExecutor-1]:MyThreadPoolTaskExecutor-1startthistask!2019-10-0113:50:17.007WARN—[lTaskExecutor-6]:MyThreadPoolTaskExecutor-6startthistask!2019-10-0113:50:17.007WARN—[lTaskExecutor-5]:MyThreadPoolTaskExecutor-5startthistask!2019-10-0113:50:17.007WARN—[lTaskExecutor-4]:MyThreadPoolTaskExecutor-4startthistask!2019-10-0113:50:17.007WARN—[lTaskExecutor-3]:MyThreadPoolTaskExecutor-3startthistask!2019-10-0113:50:17.007WARN—[lTaskExecutor-2]:MyThreadPoolTaskExecutor-2startthistask!Elapsedtime:1010
首先我们可以看到处理所有任务花费的时间大概是1s。
这与我们自定义的ThreadPoolTaskExecutor有关,我们配置的核心线程数是6,然后通过通过下面的代码模拟分配了6个任务给系统执行。
这样每个线程都会被分配到一个任务,每个任务执行花费时间是1s,所以处理6个任务的总花费时间是1s。
List<String>words=(F,T,S,Z,J,C);List<CompletableFuture<List<String>>>completableFutureList=()(word->(word))(());
试着去把核心线程数的数量改为3,再次请求这个接口你会发现处理所有任务花费的时间大概是2s。
特殊情况无需返回值
另外,从上面的运行结果可以看出,当所有任务执行完成之后才返回结果。
这种情况对应于我们需要返回结果给客户端请求的情况下,假如我们不需要返回任务执行结果给客户端的话呢?就比如我们上传一个大文件到系统,上传之后只要大文件格式符合要求我们就上传成功。
普通情况下我们需要等待文件上传完毕再返回给用户消息,但是这样会很慢。
采用异步的话,当用户上传之后就立马返回给用户消息,然后系统再默默去处理上传任务。
这样也会增加一点麻烦,因为文件可能会上传失败,所以系统也需要一点机制来补偿这个问题,比如当上传遇到问题的时候,发消息通知用户。
下面会演示一下客户端不需要返回结果的情况:
将completableFutureTask方法变为void类型@AsyncpublicvoidcompletableFutureTask(Stringstart){……//这里可能是系统对任务执行结果的处理,比如存入到数据库等等……//doSomeThingWithResults(results);}Controller代码修改如下:@GetMapping(/movies)publicStringcompletableFutureTask()throwsExecutionException,InterruptedException{//Starttheclocklongstart=();//Kickofmultiple,asynchronouslookupsList<String>words=(F,T,S,Z,J,C);()(word->(word));//Waituntiltheyarealldone//Printresults,(Elapsedtime:+(()-start));returnDone;}
请求这个接口,控制台打印出下面的内容:
Elapsedtime-10-0114:02:44.052WARN—[lTaskExecutor-4]:MyThreadPoolTaskExecutor-4startthistask!2019-10-0114:02:44.052WARN—[lTaskExecutor-3]:MyThreadPoolTaskExecutor-3startthistask!2019-10-0114:02:44.052WARN—[lTaskExecutor-2]:MyThreadPoolTaskExecutor-2startthistask!2019-10-0114:02:44.052WARN—[lTaskExecutor-1]:MyThreadPoolTaskExecutor-1startthistask!2019-10-0114:02:44.052WARN—[lTaskExecutor-6]:MyThreadPoolTaskExecutor-6startthistask!2019-10-0114:02:44.052WARN—[lTaskExecutor-5]:MyThreadPoolTaskExecutor-5startthistask!
可以看到系统会直接返回给用户结果,然后系统才真正开始执行任务。
参考引用
作者:李浩宇Alex
【后台技术】异步编程指北,问题和重点
异步编程指北:问题与重点异步编程在多任务场景中经常遇到,如:同步、异步、并发、并行与串行。
理解这些概念对于开发工作至关重要。
本文由腾讯工程师michaeywang撰写,旨在对异步编程进行全面归纳总结,为开发工作提供帮助。
一、异步编程的基本概念1. 并发与并行:并发指多个任务在同一时间段内同时执行,单核心计算机通过不断切换任务实现并发操作;并行则是多个任务在同一时刻同时执行,需要多核心计算机支持,每个核心独立执行任务。
2. 同步与异步:同步意味着多个任务开始执行后,需要全部完成才能结束;异步则允许主任务A执行完成后即视为结束,同时执行异步任务B、C,主任务A不必等待其他任务结果。
3. 并发与并行是逻辑结构设计模式,同步与异步是逻辑调用方式;串行是同步的一种实现,代表任务按顺序执行。
二、并发/并行执行的问题与挑战1. 并发任务数量控制:对于高QPS(每秒请求数)的接口,需优化性能,将数据读取和逻辑计算分离,通过并发执行优化响应时间。
例如,将1万请求优化为并发执行,充分利用多核计算机性能。
2. 共享数据读写与依赖关系:并发读写共享数据可能导致脏数据问题,需使用锁、队列等手段确保数据一致性。
同时,明确任务依赖关系,避免部分任务串行化执行。
三、状态处理与结果返回1. 忽略结果场景:对于重要程度不高或复杂度高的异步任务,可能会忽略结果处理。
数据一致性、功能可靠性需引起重视。
2. 结果返回方法:轮询查询与回调通知是状态处理的主要方法,前者适用于需要实时结果的场景,后者则具有更高实时性和效率。
四、异常处理1. 异常捕获与处理:在异步编程中,需分别对主程序与异步任务进行异常处理,避免整个进程因异常崩溃。
在golang中,需在协程中添加recover()来捕捉异常。
五、典型场景与思考总结典型场景包括订阅发布模式、慢请求与高并发任务处理等。
编程时,需平衡人脑与电脑特性,选择合适的方式进行异步思考与开发。
大多数情况下,同步方式是默认的思考方式,异步编程是特定需求下的选择。