去声明被认为有害(2018)

2021-03-20 09:03:46

每个并发API都需要一种方法并发运行代码。这里' SSOME示例看起来像使用不同的API:

go myfunc(); // golangpthread_create(& thread_id,null,& myfunc); / * c用posix线程* / spawn(moduleename,myfuncname,[])%erlangthreading.thread(target = myfunc).start()#python与threadsasyncio.create_task(myfunc()#python与asyncio

符号和术语有很多变化,但是andics是相同的:这些都将MyFunc同时安排到术语的其余部分,然后返回父母可以做其他事情。

qobject :: connect(&发射器,信号(事件()),// c ++带有qt&接收器,插槽(myfunc())g_signal_connect(发射器,"事件",myfunc,null)/ * c使用gobject * / document.getelementbyid(" myid")。onclick = myfunc; // javascriptpromise.then(myfunc,errorhandler)// javascript与promisesdeferred.addcallback(myfunc)#python with twistedfuture.add_done_callback(myfunc)#python与asyncio

同样,符号变化,但这些都完成了同样的事情:它们排列从现在开始,如果发生某个事件,则MyFunc将运行。然后一旦他们' ve设置了,他们会返回,所以呼叫者可以做其他事情。 (有时候戴着花哨的助手,如promise组合者,或扭曲式协议/运输,但核心想法是相同的。)

而......那'它。采用任何现实世界,通用的ConcurrencyAPI,你' ll可能会发现它落入一个或另一个桶(或有时都像异步)。

但我的新图书馆三重奏是奇怪的。它使用任何一种方法都使用它。相反,如果我们想同时运行myfunc和另一个职位,我们会写一些如:

当人们第一次遇到这个"托儿所"构建,他们倾向于混淆。为什么有一个缩进的块?什么'这个托儿所对象,以及在我可以产生任务之前,为什么我需要一个?然后他们意识到它可以防止他们使用它们' vegotten习惯于其他框架,他们真的很生气。 ITFEELS QUIRKY和特质,过高的级别是基本的。这些是可理解的反应!但与我忍受。

在这篇文章中,我想说服你的苗圃' t古怪的oridiosyncratic根本,而是一个新的控制流原子,即'调整为循环或函数呼叫的基本。此外,我们看到上面的其他方法 - 螺纹产卵和呼叫重复 - 应完全删除并替换含有持疗。

听起来不太可能?之前发生了类似的东西:Goto声明曾经是控制流的王。现在它' s一个妙语。几种语言仍然是他们称之为Goto,但它的不同和较弱的Thanthe原始Goto'而且大多数语言Don' t甚至有那个。发生了什么?很久以前那么大多数人都不再熟悉这个故事,但事实证得令人惊讶地相关。 SOWE' LL首先提醒自己,完全是什么,安直看到它可以教我们关于并发性API的内容。

让'查看一些历史记录:使用汇编语言或其他甚至更多的原始机制编程早期计算机。这有点吸了。因此,在20世纪50年代,Peoplelike John Backus Atibm和Grace Hopperat Remington Rand开始开发像Fortran和Flow-Matic这样的语言(因为它为其直接继任Cobol而闻名)。

流动matic是雄心勃勃的时间。你可以想到它aspython' s伟大的大伟大 - ... - 祖父母:第一语言为人类设计,电脑为第二种语言。在这里' s某种流动的代码,让您品尝它看起来像的东西:

你'请注意,与现代语言不同,在这里,如果块,循环块或函数呼叫,实际上是那里的块,循环块或函数根本没有块状物或缩进。它只是一个平面的陈述列表.That' s不是因为这个程序恰好太短暂,无法使用fanciercontrol语法 - 它' s,因为块语法' t尚未发明!

序列流量表示为垂直箭头指向,并且GOTO流量表示为箭头,起步下来,然后跳到侧面。

相反,流动MINT有两个用于流量控制的选项。通常情况下,它是顺序的,就像你一样' D期望:从顶部和移动,一次一个陈述。但是,如果执行像跳转一样的专业名称,那么它可以直接转移控制器其他地方。例如,语句(13)跳回声明(2):

就像我们开始的并发原语一样,有关于将此称之为何种方式的大量分歧"做单向跳跃"操作。它跳转到,但跳到了,但困住的名字是转到的(比如"去"得到它?),所以和#39; s什么我' ll在这里使用。

如果你认为这看起来很困惑,你'不孤单!这种基于jump的编程的样式是流动 - matic incontited pastymuch直接从汇编语言。它'强大,良好的拟合电脑硬件实际上是有效的,但它的超级混淆直接工作。箭头的纠结是为什么这个词和#34;意大利面条代码"是发明的。显然,我们需要更好的东西。

但是...... Goto是什么导致所有这些问题? Whyare一些控制结构好,有些不是?我们如何挑着套对?当时,如果你不了解它,这真的不清楚,'如果你不理解它,难以解决一个问题。

但是,让'在历史上暂停暂停一会儿 - 每个人都知道转到很糟糕。这与并发性有什么关系?好吧,考虑Golang'着名的Go声明,习惯了一个新的" Goroutine" (轻质线程):

我们可以绘制其控制流程图吗?好吧,它'从我们看过的任何一个都是一个小型的东西,因为控制暂停了分裂。我们可能会像:

"去"流量表示为两个箭头:绿色箭头指向,以及开始指向侧面的薰衣草箭头,然后向侧面向上。

这里的颜色旨在指示两条路径。从父goroutine(绿线)的角度来看,控制流程顺序:它进入顶部,然后立即汇出底部。同时,从孩子的角度(Lavenderline),控制进入顶部,然后跳到MyFunc的身体。与常规函数调用不同,此跳转是单向的:当Running MyFunc我们切换到一个全新的堆栈,并且运行立场终止忘记我们来自哪里。

但这并不是申请戈兰。这是我们在本柱开始时列出的所有基元的流量兼容兼容性:

线程库通常提供某种您稍后加入线程的句柄对象 - 但这是一种独立的语言,语言没有知道。实际产卵原语具有上述控制流程。

注册回调是语义上等同于启动abackground线程(a)块,直到发生一些事件,并且然后(b)运行回调。 (虽然显然,实施是有关的。)因此,在高级控制流程方面,注册acallback基本上是一个go语句。

期货和承诺也是一样的:当你召唤函数并返回一个承诺,这意味着它' s计划发生在后台的工作,然后给你一个句柄对象以稍后加入Thework的句柄对象(如果需要)。在控制流程语义方面,此目的就像产卵一个线程一样。然后,您注册了摘要的回调,因此请参阅上一个项目符号。

这种相同的明确模式在许多情况下显示出多种形式:keysiemillity在所有这些情况下,控制流量拆分,其中一个单向跳转和另一侧返回给呼叫者。您知道要查找的内容,你' ll开始在地球上看到它 - 它' sa fun game! [1]

但是,令人讨厌的是,这个类别的Constrol Flow构造没有标准名称。所以就像" goto声明"成为所有不同的Goto的构建体,I&#39的题柱术语;使用" Go声明"作为这些伞术语。为什么要去?一个原因是戈兰给我们一个特别纯粹的例子。而另一个是......好吧,你可能猜到了所有的猜测。看看这两个图。通知任何激励吗?

重复较早的图:转到的流量表示为箭头,开始指向向下,然后跳到侧面,和#34; Go"流量表示为两个箭头:绿色箭头指向,以及开始指向侧面的薰衣草箭头,然后向侧面向上。

并发计划尚不难以编写和原理。所以基于GOTO的程序。是否有可能出于一些同样的原因?在现代语言中,Goto的问题很大程度上得到了解决。如果我们研究他们如何修复到达,它会教我们如何制作更具可用的并发性API?让'发现。

那么Goto是什么意思,这使得它导致这么多问题? 20世纪60年代末期,Edsger W. Dijkstra写了一对非法着名的论文,帮助让这更清楚:转到陈述的有害,以及结构化编程(PDF)的注意事项。

在这些论文中,Dijkstra担心Hywrite非琐碎软件的问题并获得正确的问题。我可以'在这里给予他们的职责;有各种迷人的见解。例如,您可能已经听过此报价:

是的,从结构化编程的笔记中,&#39。但他的主持人是抽象的。他想写一下Toobig的程序一次。为此,您需要像黑匣子一样对待该程序 - 就像你看到PythonProgram一样:

然后你不需要了解如何打印Isimplemented的所有细节(字符串格式,缓冲,跨平台差异,......)。您只需要知道它会以某种方式打印文本yougive它,然后您可以花费你的能量思考这一点,在您的代码中您想要发生的情况.Dijkstra想要的语言来支持这种情况抽象。

到目前为止,已经发明了块语法,语言LikeAlgol积累了〜5种不同类型的控制结构:它们有顺序流量和转到:

您可以使用GOTO,ANDEARLY on,'人们如何想到它们:作为一个促进者,可以实现这些更高级别的构造但是Dijkstra指出的是,如果你看看奇异图,那么Goto和其他人之间有很大的区别。 Foreverything除了GOTO,流量控制进入顶部→[stamphappens]→流量控制出来底部。我们可能会致电这个"黑匣子规则&#34 ;:如果控制结构有这种形状,那么无论你在哪里都在哪里' t关心幸福的内容的细节,你可以忽略[东西发生]部分,并以定期顺序流量对待换取的东西。甚至更好,这是任何代码的alsotrue' s由这些碎片组成。当我看这个代码时:

我不得不阅读打印和所有意外依赖性的定义,只是为了弄清楚控制流程的方法.Meybe内部打印那里' SA循环,以及在那里的循环内部,在If / ides中,在那里的另一个函数调用...... ormaybe它' s别的东西。它并不是重视:我知道控制势力进入打印,这个功能会做它的东西,然后文本控制将回到代码i'读。

它看起来很明显,但如果你有一个goto的语言 - 一种函数和其他一切都建立了到达的东西的语言,并且转到可以随时随地跳跃 - Thenthese控制结构aren' t黑色盒子!!如果您有一定功能,并且在其中的功能内部' sa循环,以及在Loopthere' s中的一个,以及在If / else中,在If / else中有#39; sa got ... thenthat goto可以发送控件它想要的任何地方。也许控制器完全突然从另一个函数返回,一个你没有' Teven叫做。你不知道!

并且这突破了抽象:这意味着每个函数调用伪装中的GOTO语句,唯一的方式是要将系统的整个源代码保持在头部atonce。只要转到了你的语言,你就可以停止能够关于流量控制的Dolocal推理。这'为什么转到罗h t h。

现在Dijkstra了解这个问题,他能够解决它。他的革命性提案:我们应该停止思考off /循环/函数调用作为转到的速记,而是在他们自己的权利中的非法原语 - 我们应该完全从语言中删除Goto。

从2018年开始,这似乎很明显。但是你看过如何在你试图带走玩具时如何做出反应,因为它们' renot smart足够聪明地安全使用它们?是的,有些事情永远不会改变。1969年,这个提议是令人难以置信的争议。 Donald Knuth捍卫了转达。已成为书面代码专家的人合理地憎恨,必须基本上学习如何将他们的想法表达使用较新的潮流构建构建。当然,它需要构建一套背包的语言。

最终,现代语言对此Thandijkstra&#39的原始制定有点严格。它们' LL让您使用像休息,继续或返回等构造,让您立即突出多平台结构。但从根本上讲,他们'重新设计所有设计的dijkstra'我们的想法;即使这些构造也只按照严格限制的方式推动界限。特别是,函数 - 围绕黑箱内包装控制流的基本工具 - 被认为是不可侵入的。你可以' t and tonefunction和另一个人分手,返回可以把你带出你的函数,但没有进一步。无论控制流动的行流,Shenanigans一定是在内部启动,其他功能不得不关心。

这甚至延伸到Goto本身。你' ll找到几种语言,instill有一些他们称之为的东西,就像c,c#,golang,... butthey' ve增加了严重的限制。至少,他们赢得了'那么让你跳出一个功能体并进入另一个功能。除非你'在装配[2]中重新加工[2],经典,不受限制的转到走了.dijkstra赢了。

一旦转到消失了,发生了一些有趣的事情:语言设计师能够开始添加依赖于结构化的功能的功能。

例如,Python具有一些很好的资源清理语法:使用语句。你可以写这样的东西:

并且它保证了文件在...代码期间将打开,但之后立即关闭。大多数现代语言都有SomeeQuivalent(Raii,使用,尝试资源,推迟,......)。并所有人都假定控制有序,结构化的方式流动。如果我们使用goto跳入我们的块中间......甚至会做什么?文件是否打开?如果我们跳过外面,而不是正常退出怎么办?文件会关闭吗?如果您的语言在其中格式化,这只事图就在任何连贯的方式工作。

错误处理有类似的问题:当出现问题时,你的代码是什么?答案通常是将COUNK上调到代码'呼叫者,让他们弄清楚如何处理它。现代语言具有专门的构造,以便使这更容易,更像,或其他形式的自动错误传播。但是如果它有一个堆栈,并且可靠的概念,你的语言只能提供这个帮助。呼叫者"在我们的流动模拟中再次查看控制弗洛伊盖章,并想象在它中间,它试图提出异常。它甚至会去哪里?

所以转到 - 传统的形式,忽略了函数边界 - 是常规类型的坏功能,那种' s坚硬的速度正确。如果是,它可能会幸存下来 - 很多糟糕的成果。但它'更糟糕。

即使你不使用goto自己,也只是把它作为一种选择,你的语言使一切难以使用。每当你开始第三方库时,你就可以&#39把它视为一个黑匣子 - 你读到一切都知道哪些功能是规范功能,哪些功能是特殊流量控制构造的结构。这是当地推理的严重障碍。而且您的Losepower语言功能如可靠的资源清理和自动错误传播。更好地完全去除Goto,介绍"黑匣子&#34的控制流量构造规则。

所以' soto的历史。现在,这有多少适用于陈述?好吧......基本上,所有的!比喻变得令人震惊的精确。

GO陈述中断抽象。还记得我们如何说,如果我们的语言允许转到,那么任何功能都可能是Goto IndisGuise?在大多数并发框架中,Go语句会导致此操作相同的问题:每当您调用函数时,它可能会或可能会产生一些后台任务。这个函数似乎返回,但仍然在后台运行?没有办法知道没有其源代码过境的所有源代码。什么时候结束?很难说。如果您有Go陈述,则函数不受控制流程的长框。在我的第一个发布帖子onconcurrency apis中,我打电话给这个"违反了因果关系",发现它是使用Asyncio Andtwisted的程序中许多常见的现实世界问题的rootiaause,就像背压一样,关闭问题的问题等等。

之前,我们说我们是"保证"该文件将是openwhile ...代码正在运行,然后之后关闭。但是如果...代码产生后台任务?然后我们的保证丢失了,看起来像它们的操作'在用块结束后,re在with块内部可能会继续运行,因为文件在它们仍然使用它时关闭。再次,您可以&从本地检​​查中#39;要知道这个Ishappening是否必须将源代码读到...代码内的所有功能。

如果我们希望这段代码正常工作,我们需要以某种方式跟踪任何后台任务,并手动安排文件仅在他们完成时才成为唯一的文件。它'除非我们' refumentsome图书馆,除非' t,它没有提供任何方法在完成时收到通知,这是令人作呕的常见(例如,因为它是它揭露了您的任何任务处理可以加入)。但即使在最明白的情况下,非结构化控制流程也意味着语言可以' t HOLKUS。我们'重新返回手用手实施资源清理,就像在过去的日子里一样。

Go陈述中断错误处理。与上面讨论的那样,现代语言提供了强大的工具,如例外情况,以帮助解除错误检测到错误并将其传播到正确的位置。但这些工具依赖于具有可靠的&#34的概念;当前代码'呼叫者&#34 ;。只要你产生任务或注册回调,那个概念就被打破了。因此,我知道的每个主流康复框架都只是放弃。如果在Abackground任务中发生错误,并且您手动处理它,那么运行时会将其放在地板上,并将其手指交叉,它是' ttoo重要。如果你'重新幸运,它可能会在TheConsole上打印一些东西。 (唯一的其他软件i' ve使用它的想法" printsomething并继续前进"是一个很好的错误处理策略是Glottyold Fortran图书馆,但在这里我们是。)甚至生锈 - 大多数象牙

......