热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:探秘JS的异步单线程

篇首语:本文由编程笔记#小编为大家整理,主要介绍了探秘JS的异步单线程相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了探秘JS的异步单线程相关的知识,希望对你有一定的参考价值。




对于通常的developer(特别是那些具备并行计算/多线程背景知识的developer)来讲,js的异步处理着实称得上诡异。而这个诡异从结果上讲,是由js的“单线程”这个特性所导致的。

我曾尝试用“先定义后展开”的教科书方式去讲解这一块的内容,但发现极其痛苦。因为要理清楚这个东西背后的细节,并将其泛化、以更高的视角来看问题,着实涉及非常多的基础知识。等到我把这些知识讲清楚、讲完,无异于逼迫读者抱着操作系统、计算机网络这样的催眠书看上好个几章节,着实沉闷而乏味。

并且更关键的是,在走到那一步的时候,读者的精力早已消耗殆尽,完全没有心力再去关心这个最开始的问题——js的异步处理为何诡异。

所以,我决定反过来,让我们像一个初学者那样,从一无所知开始,



先使用“错误的理念”去开始我们的讨论,然后用代码去发现和理念相违背的地方。

再做出一些修正,再考察一些例子,想想是否还有不大满意和清楚的地方,再调整。如此往复,我们会像侦探那样,先从一个不大正确的假设开始,不断寻找证据,不断修正假设,一步步追寻下去,直到抵达最后完整的真相。

我想,这样的写作方式,更符合一个人真正的求知和研究过程,并能够为你带来更多关于“探索问题”的启发。我想,这样的思维方式和研究理念,比普通的知识更为重要。它能够让你成为知识的猎人,有能力独立地觅食,而不必被迫成为婴孩,只能坐等他人喂食。

好了,让我们先从一块js代码,开始我们的探索之旅。

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 5000);
console.log(‘No. 2‘);

输出结果是:

No. 1
No. 2
setTimeout callback

这块代码中几乎没什么复杂的东西,全是打印语句。唯一的特别是函数setTimeout,根据粗略的网上资料显示,它接受两个参数:



  • 第一个参数是callback函数,就是让它执行完之后,回过头来调用的函数。

  • 另一个是时间参数,用于指定多少微妙之后,执行callback函数。这里我们使用了5000微妙,也即是5秒钟。

另一个重点是,setTimeout是一个异步函数,意思是我的主程序不必去等待setTimeout执行完毕,将它的运行过程扔到别的地方执行,然后主程序继续往下走。也即是,主程序是一个步调、setTimeout是另一个步调,即是“异步”的方式跑代码。

如果你有一些并行计算或者多线程编程的背景知识,那么上面的语句就再熟悉不过了。如果在多线程环境,无非是另起一根线程去运行打印语句console.log(‘setTimeout callback‘)。然后主线程继续往下走,新线程去负责打印语句,清晰明了。

所以综合起来,这段代码的意思是,主线程执行到语句setTimeout时,就把它交给“其它地方”,让这个“其它地方”等待5秒钟之后运行。而主线程继续往下走,去执行“No. 2”的打印。所以,由于其它部分要等待5秒钟之后才运行,而主线程立刻往下运行了“No. 2”的打印,最终的输出结果才会是先打印“No. 2”,再打印“setTimeout callback”。

嗯,so far so good。一切看来都比较美好。

如果我们对上述程序做一点变动呢?例如,我可不可以让“setTimeout callback”这个信息先被打印出来呢?因为在并行计算中,我们经常遇到的问题便是,由于你不知道多个线程之间谁执行得快、谁执行得慢,所以我们无法判定最终的语句执行顺序。这里我们让“setTimeout callback”停留了5秒钟,时间太长了,要不短一点?

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 1);
console.log(‘No. 2‘);

我们将传递给setTimeout的参数改成了1毫秒。多次运行后会发现,结果竟然没有改变?!似乎有点反常,要不再改小一点?改成0?

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 0);
console.log(‘No. 2‘);

多次运行后,发现依旧无法改变。这其实是有点奇怪了。因为通常的并行计算、多线程编程中,通过多次运行,你其实是可以看到各种无法预期的结果的。在这里,竟然神奇地得到了相同的执行顺序结果。这就反常了。

但我们还无法完全下一个肯定的结论,可不可能因为是setTimeout的启动时间太长,而导致“No. 2”这条语句先被执行呢?为了做进一步的验证,我们可以在“No. 2”这条打印语句之前,加上一个for循环,给setTimeout充分的时间去启动。

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 0);
for (let i = 0; i <10e8; i++) {}
console.log(‘No. 2‘);

运行这段代码,我们发现,"No. 1"这条打印语句很快地显示到了浏览器命令行,等了一秒钟左右,接着输出了

No. 2
setTimeout callback

诶?!这不就更加奇怪了吗?!setTimeout不是等待0秒钟后立刻运行吗,就算启动再慢,也不至于等待一秒钟之后,还是无法正常显示吧?况且,在加入这个for循环之前,“setTimeout callback”这条输出不是立刻就显示了吗?

综合这些现象,我们有理由怀疑,似乎“setTimeout callback”一定是在“No. 2”后显示的,也即是:setTimeout的callback函数,一定是在console.log(‘No. 2‘)之后执行的。为了验证它,我们可以做一个危险一点的测试,将这个for循环,更改为无限while循环。

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 0);
while {} // dangerouse testing
console.log(‘No. 2‘);

如果setTimeout的callback函数是按照自己的步调做的运行,那么它就有可能在某个时刻打印出“setTimeout callback”。而如果真的是按照我们猜测的那样,“setTimeout callback”必须排在“No. 2”之后,那么浏览器命令行就永远不会出现“setTimeout callback”。

运行后发现,在浏览器近乎要临近崩溃、达到内存溢出的情形下,“setTimeout callback”依旧没有打印出来。这也就证明了我们的猜测!

这里,我们第一次出现了理念和现实的矛盾。按照通常并行计算的理念,被扔到“其它地方”的setTimeout callback函数,应该被同时运行。可事实却是,这个“其它地方”并没有和后一条打印“No. 2”的语句共同执行。这时候,我们就必须要回到基础,回到js这门语言底层的实现方式上去追查,以此来挖掘清楚这后面的猫腻。

js的特性之一是“单线程”,也即是从头到尾,js都在同一根线程下运行。或许这是一个值得调查深入的点。想来,如果是多线程,那么setTimeout也就该按照我们原有的理念做执行了,但事实却不是。而这两者的不同,便在于单线程和多线程上。

找到了这个不同点,我们就可以更深入去思考一些细节。细想起来,所谓“异步”,就是要开辟某个“别的地方”,让“别的地方”和你的主运行路线一起运行。可是,如果现在是单线程,也就意味着计算资源有且只有一份,请问,你如何做到“同时运行”呢?

这就好比是,如果你去某个办事大厅,去缴纳水费、电费、天然气。那么,我们可以粗略地将它们分为水费柜台、电费柜台、天然气柜台。那么,如果我们依次地“先在水费柜台办理业务,等到水费的明细打印完毕、缴纳完费用后;再跑去电费柜台打印明细、加纳费用;再跑去天然气柜台打印明细、加纳费用”,这就是一个同步过程,必须等待上一个步骤做完后,才能做下一步。

而异步呢,就是说我们不必在某个环节浪费时间瞎等待。比如,我们可以在“打印水费明细”的空闲时间,跑到电费和天然气柜台去办理业务,将“电费明细、天然气明细的打印”这两个任务提前启动起来。再回过头去缴纳水费、缴纳电费、缴纳天然气费用。其实,这就是华罗庚推广优选法的时候举的例子,烧水、倒茶叶、泡茶,如何安排他们的顺序为高效。

显然,异步地去做任务更高效。但这要有一个前提,就是你做任务的资源,也即是干活的人或者机器,得有多份才行。同样按照上面的例子来展开讨论,虽然有水费、电费、天然气这三个柜台,可如果这三个柜台背后的办事人员其实只有一个呢?比如你启动了办理水费的业务,然后想要在办理水费业务的等待期,去电费柜台办理电费业务。表面上,你去电费柜台下了申请单,请求办理电费业务,可却发现根本没有办事员去接收你的这个业务!为何?因为这有且只有一个的办事员,还正在办理你的水费业务啊!这时候,你的这个所谓的“异步”,有何意义?!

所以从这个角度来看,当计算资源只有一份的时候,你做“异步”其实是没什么意义的。因为干活的资源只有一份,就算在表面做了名义上的“异步”,可最终就像上面的多柜台单一办事员那样,到了执行任务层面,还是会一个接一个地完成任务,这就没有意义了。

那么,js的特性是”单线程“+”异步“,不就正是我们讨论的“没有意义”的情况吗?!那又为何要多次一举,干一些没有意义的事情呢?

嗯......事情变得越来越有趣了。

通常来讲,如果一个事件出现了神奇和怪异的地方,基本上都是因为我们忽略了某个细节,或者对某个细节存在误解或是错误理解。要想把问题解决,我们就必须不断地回顾已有材料,在不断地重复检验中,发现那几根我们忽略的猫腻。

让我们回顾一下关于js异步的宣传片。通常为了说明js异步的必要性,会举出浏览器的资源加载和页面渲染这个矛盾。


渲染,可以比较粗糙地理解为将“画面”画出来的过程。例如,浏览器要将页面上的按钮、图片显示出来,就必须有一个将“图片”在网页上画出来的动作。又或是,操作系统要将“桌面”这个图形界面显示在显示器上,就必须要把它相应的这个“画面”在显示器上画出来的动作。归结起来,这个“画出来”的过程,就被称之为“渲染”。

例如,你点击页面上的一个button,让浏览器去后端数据库将数据报表取出来,在网页上把数字显示出来。而如果js不支持异步的话,整个网页的就会停留,也即是“卡”,在鼠标点击按钮这一个动作上,页面无法完成后续的渲染工作。一直要等到后端把数据返回到了前端,程序流才能够继续跑下去。

所以这里,js的“异步”其实是为了让浏览器将“加载”这个任务分给“其它地方”,让“加载过程”和“渲染过程”同步进行下去。

等等,又是这个“其它地方”?!!

我擦,不是说js是单线程而么,计算资源不是只有一份么,怎么又可以“一边加载、一边渲染”了?!WTF,你这是在逗我玩儿么?!

艹,到底这里面哪句话是真的?!到底js是单线程是真的?还是说浏览器可以同时做“一边加载、一边渲染”这个事情是真的?!

如何才能解决这个疑惑?!很显然,我们必须要深入到浏览器的内部,去看一看它到底是怎么样被设计的。

在搜索引擎中,做一些关于浏览器和js的搜索,我们不难得到一些基本信息。js并不是浏览器的全部,浏览器要掌管的事情太多了,掌管js的只是浏览器的一个组件,叫做js引擎。而最出名的、并在Chrome中使用的,就是大名鼎鼎的V8引擎,它负责js的解析和运行。

另一方面我们还知道,使用js的一个很大原因,是因为它能够自由地去操控DOM元素、去执行Ajax异步请求、能够像我们最开始举的例子那样,使用setTimeout做异步任务分配。这些都是js优秀特性。

可令人惊讶的事情来了,当我们去探索这个掌管js一切的V8引擎的时候,我们却发现,它并不提供DOM的操控、Ajax的执行以及setTimeout的特性:

技术图片

然后,让我们执行第一条语句console.log(‘Hi‘),也即是将它压入到call stack中:

技术图片

然后js引擎执行stack中最上层的这条语句。相应的,浏览器的控制台就会打印出信息“Hi”:

技术图片

由于这条语句被执行了,所以它也从stack中消失:

技术图片

再来压入第二条语句setTimeout

技术图片

执行setTimeout(function cb1() { console.log(‘cb1‘); }, 5000);

技术图片

注意,由于setTimout部分并没有被包含在js引擎中,所以它就直接被扔给了Web APIs的Timeout部分。这里,stack中的蓝色部分起到的作用,就是将相应的内容“timer、等候时间5秒、回调函数cb1”扔给Web APIs。然后这条语句就可以从stack中消失了:

技术图片

继续压入下一条语句console.log(‘Bye‘)

技术图片

注意,此时在Web APIs的部分,正在并行于js引擎执行相应的语句,即:等候5秒钟。Okay,timer继续它的等待,而stack这边已经有语句了,所以需要把它执行掉:

技术图片

相应的浏览器控制台,就会显示出“Bye”的信息。而stack中运行后的语句,就该消失:

技术图片

此时,stack已经为空。Event loop检测到stack为空,自然就想要将callback queue中的语句压入到stack中。可此时,callback queue中也为空,于是Event loop只好继续循环检测。

另一方面,Web APIs这边的timer,并行地在5秒钟后开始了它的执行——什么也不做。然后,将它相应的回调函数cb1(),放到callback queue中:

技术图片

Event loop由于一直在循环检测,此时,看到callback queue有了东西,就迅速将它从callback queue中取出,然后将其压入到stack里:

技术图片

现在Stack里有了东西,就需要执行回调函数cb1()。而cb1()里面调用了 console.log(‘cb1‘)这条语句,所以需要将它压入stack中:

技术图片

stack继续执行,现在它的最上层是 console.log(‘cb1‘),所以需要先执行它。于是浏览器的控制它打印出相应的信息“cb1”:

技术图片

将执行了的 console.log(‘cb1‘)语句弹出栈:

技术图片

继续执行cb1()剩下的语句。此时,cb1()已经没有其它需要执行的语句了,也即是它被运行完毕,所以,将它也从stack中弹出:

技术图片

整个过程结束!如果从头到尾看一遍的话,就是下面这个gif图了:

技术图片

相当清晰直观,对吧!

如果你想进一步地把玩js的语句和call stack、callback queue的关系,推荐Philip Roberts的一个GitHub的开源项目:Loupe,里面有他online版本供你做多种尝试。

有了这些知识,现在我们回过头去看开头的那段让人产生疑惑的代码:

console.log(‘No. 1‘);
setTimeout(function(){
console.log(‘setTimeout callback‘);
}, 0);
console.log(‘No. 2‘);

按照上面的js处理语句的顺序,第一条语句console.log(‘No. 1‘)会被压入stack中,然后被执行的是setTimout

根据我们上面的知识,它会被立刻扔进Web APIs中。可是,由于这个时候我们给它的等待时间是0,所以,它的callback函数console.log(‘setTimeout callback‘)会立刻被扔进“Callback Queue”里面。所以,那个传说中的“其它地方”指的就是callback queue。

那么,我们能够期望这一条console.log(‘setTimeout callback‘)先于“No. 2”被打印出来吗?

其实是不可能的!为什么?因为要让它被执行,首先它需要被压入到call stack中。可是,此时call stack还没有将程序的主分支上的语句执行完毕,即还有console.log(‘No. 2‘)这条语句。所以,event loop在stack还未为空的情况下,是不可能把callback queue的语句压入stack的。所以,最后一条“setTimeout callback”的信息,一定是会排在“No. 2”这条信息后面被打印出来的!

这完全符合我们之前加入无限while循环的结果。因为主分支一直被while循环占有,所以stack就一直不为空,进而,callback queue里的打印“setTimeout callback”的语句就更不可能被压入stack中被执行。

探索到这里,似乎该解决的问题也都解决了,好像就可以万事大吉,直接封笔走人了。可事实却是,这才是我们真正的泛化讨论的开始!

做研究和探索,如果停留于此,就无异于小时候自己交作业给老师,目的仅仅是完成老师布置的任务。在这里,这个老师布置的任务就是文章开头所提出的让人疑惑的代码。可是,解决这段代码并不是我们的终极目的。我们需要泛化我们的所学和所知,从更深层次的角度去探索,为什么我们会疑惑,为什么一开始无法发现这些潜藏在表面之下不同。我们要继续去挖掘,我们到底在哪些最根本的问题上出现了误解和错误认识,从而导致我们一路如此辛苦,无法在开头看到事情的真相。

回顾我们的历程,一开始让我们载跟斗的,其实就是对“异步”和“多线程”的固定假设。多线程了,就是异步,而异步了,一定是多线程吗?我们潜意识里是很想做肯定回答的。这是因为如果异步了,但却是单线程,整个异步就没有意义了(回忆那个多柜台、单一办事员的例子)。可js却巧妙地运用了:使用异步单线程去分配任务,而让真正做数据加载的Ajax、或者时间等待的setTimeout的工作,扔给浏览器的其它线程去做。所以,本质上js虽然是单线程的,可在做实际工作的时候,却利用了浏览器自身的多线程。这就好比是,虽然是多柜台、单一办事员,可办事员将缴纳电费、水费的任务,外包给其它公司去做,这样,虽然自己仍然是一个办事员,但却由于有了外包服务的支持,依旧可以一起并行来做。

另一方面,js的异步、单线程的特性,逼迫我们去把并行计算中的“同步/异步、阻塞/非阻塞”等概念理得更清楚。

“同步”的英文是synchronize,但在中文的语境下,却很容易和“同时”挂钩。于是,在潜意识里有可能会有这样一种联想,“同步”就是“同时”,所以,一个同步(synchronize)的任务就被理解为“可以一边做A,一边做B”。而这个潜意识的印象,其实完全是错误的(一般做A一边做B,其实是“异步”+“并行”的情况)。

但在各类百科词典上,确实有用“同时”来作为对“同步”的解释。这是为什么呢?其实这是对”同步“用作”同时“的一个混淆理解。如果仔细考虑”同时“的意思,细分起来,其实是有两种理解:



  • 同一个时刻(at the same time),例如在9:00 a.m这个时间点,我们既在做A也在做B。

  • 另一个是同一个时间参考系,也就是所谓的clock on the wall是同一个。

前者很容易理解,这里我重点解释一下后者。例如,我在中国大陆同美国的一个同学开微信语音聊天,我这边是22:00,他那边是9:00。我们做聊天这件事情的时候,是同一时刻(at the same time),但却不在同一个时间参考体系(clock on the wall)。而在计算机中讨论的同步,其实讨论的是后者的”同一参考系“,同步,就是让我们的参考系统一起来,放到同一个体系之下。

又比如,我们在生活中很容易说,同步你的电脑、同步你的手机通讯录、同步你的相册,说的是什么呢?就是让你的各个客户端:PC、手机,同server端服务器的内容都保持一致,也即是大家都被放到一个一致的参考系里面。不要说,你在PC里有照片A,而在手机里没有A却有B,这个时候,谈论PC里信息人与谈论手机里信息的人,就是在鸡同鸭讲。究其原因,就是没有把大家放到同一个参考系里面。

所以,同步synchronize所指的”同时“,是大家把墙上的时钟都调整到一致、调整为同一个步调,也即是同时、同一时间参考系的意思。而不是说,让事情在同一时刻并列发生。自然的,什么是异步(asynchronize)呢,异步就是大家的时间参考系是不同的,例如我在中国大陆、你在美国,我们的时间参考体系是不同的,这就是异步,不在同一个步调、频段上。

事实上,每一个独立的人、每一块独立的计算资源,它都代表了一个各自的参考体系。只要你将任务分发给了其他人或是其它计算资源,此时,就出现了两个参考体系:一个是原有主分支的参考体系,另一个是新的计算资源的参考体系。在并行计算中,有一个同步机制是使用语句barrier,目的是让所有的计算分支在这一个位置节点都完成了计算。为什么说它是一种同步机制?按照我们统一参考体系的理解,就是保证其他所有计算分支完成计算,也就保证了其它分支的消失,从而只剩下主分支这一个参考体系。于是大家可以谈论同样的东西,说同样的话,不会有误解。

另一方面,如果要更深入地理解js的设计,我认为还需要回到计算机历史的初期,例如那个只有单核的分时系统的时代。在那样一个时代,操作系统所受到的硬件上的限制,不亚于js引擎在浏览器中所受到的限制。在同样的限制下,曾经的操作系统会如何去巧妙运用那极为有限的计算资源,让整个操作系统给人以平滑、顺畅和功能强大的错觉?我想,js的这些设计必定和操作系统早期的设计紧密相关。所以在这个层面上,它将再一次回到操作系统这样的基础知识上。能否吃透现代的技术,其实很大层面上取决于你是否吃透了设计的历史,是否理解在那些资源枯竭的年代,各路大神是如何巧妙地逢山开路,遇水搭桥。无论现代的计算机硬件资源有多么丰富,它必定会因为目标的主次关系、业务的主次关系受到限制。而如何在限制中跳舞和创造,这是能够贯穿整个历史的共同性问题。

???

Reference:



  • How Javascript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await

  • asynchronous vs non-blocking

  • 非同步(Asynchronous)與同步(Synchronous)的差異

  • Philip Roberts: What the heck is the event loop anyway? | JSConf EU


???

???
近期回顾

《没有idea这把米,怎么炊熟创业这碗饭》
《2018年08月写字总结》
《财务自由所虚构的妄念》
如果你喜欢我的文章或分享,请长按下面的二维码关注我的微信公众号,谢谢!

技术图片


更多信息交流和观点分享,可加入知识星球:

技术图片



推荐阅读
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • FineReport平台数据分析图表显示部分系列接口的应用场景和实现思路
    本文介绍了FineReport平台数据分析图表显示部分系列接口的应用场景和实现思路。当图表系列较多时,用户希望可以自己设置哪些系列显示,哪些系列不显示。通过调用FR.Chart.WebUtils.getChart("chartID").getChartWithIndex(chartIndex).setSeriesVisible()接口,可以获取需要显示的系列图表对象,并在表单中显示这些系列。本文以决策报表为例,详细介绍了实现方法,并给出了示例。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Android源码中的Builder模式及其作用
    本文主要解释了什么是Builder模式以及其作用,并结合Android源码来分析Builder模式的实现。Builder模式是将产品的设计、表示和构建进行分离,通过引入建造者角色,简化了构建复杂产品的流程,并且使得产品的构建可以灵活适应变化。使用Builder模式可以解决开发者需要关注产品表示和构建步骤的问题,并且当构建流程发生变化时,无需修改代码即可适配新的构建流程。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 【爬虫】关于企业信用信息公示系统加速乐最新反爬虫机制
    ( ̄▽ ̄)~又得半夜修仙了,作为一个爬虫小白,花了3天时间写好的程序,才跑了一个月目标网站就更新了,是有点悲催,还是要只有一天的时间重构。升级后网站的层次结构并没有太多变化,表面上 ... [详细]
  • 在本教程中,我们将看到如何使用FLASK制作第一个用于机器学习模型的RESTAPI。我们将从创建机器学习模型开始。然后,我们将看到使用Flask创建AP ... [详细]
  • POCOCLibraies属于功能广泛、轻量级别的开源框架库,它拥有媲美Boost库的功能以及较小的体积广泛应用在物联网平台、工业自动化等领域。POCOCLibrai ... [详细]
author-avatar
张怡如秋岳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有