同步性是一个神话

大家好!

今天,您会发现没有文本的长文本(与原始文本相比略有缩短),在标题中详细分析了论文。 微软资深人士特里·克劳利Terry Crowley)描述了异步编程的本质,并解释了为什么这种方法比同步和顺序的方法更现实,更合适。

那些希望或想写一本涉及此类主题的书的人-个人写作。

同步性是一个神话。 一切都不会立即发生。 一切都需要时间。
计算系统和编程环境的某些特征基本上是基于这样的事实,即计算是在三维物理世界中进行的,并且受到光速和热力学定律的限制。

在物理世界中的这种扎根意味着即使在新技术的出现下,某些方面也不会失去其相关性,这些新技术提供了新的机会,并进入了生产力的新领域。 它们之所以保持有效,是因为它们不仅是“设计期间选择的选项”,还是物理宇宙的内在现实。

语言中的同步和异步与系统创建之间的区别只是具有深厚物理基础的设计方面。 大多数程序员会立即开始使用此类程序和语言,这意味着需要同步执行。 实际上,这是如此自然,以至于没有人直接提及或谈论它。 在此上下文中,术语“同步”是指计算立即进行,就像一系列连续的步骤一样,在完成之前没有其他事情发生。 我执行“c = a + b” “x = f(y)” -在此指令完成之前,将不会发生其他任何事情。

当然,物理宇宙不会立即发生任何事情。 所有进程都与某些延迟相关联-您需要导航内存层次结构,执行处理器周期,从磁盘驱动器读取信息或通过网络连接到另一个节点,这也会导致传输数据的延迟。 所有这些都是光速和信号传播在三个维度上的根本结果。

所有过程都有些迟;一切都需要时间。 在定义某些过程为同步过程时,我们实质上是说我们将忽略此延迟,并将我们的计算描述为瞬时的。 实际上,在计算机系统中,通常会建立严格的基础结构,即使您试图通过将同步事件呈现在事件上来优化编程接口,也可以使他们继续积极使用基本硬件。

同步是通过一种特殊的机制提供的,并且涉及到成本,这种想法对于程序员来说似乎是不合逻辑的,程序员更习惯于这样的事实,即异步需要主动的外部控制。 实际上,这是在提供异步接口时实际发生的情况:真正的基本异步向程序员开放的程度比以前更加清晰,并且他必须手动处理它,而不是依靠可以自动执行此操作的程序。 直接提供异步功能会给程序员带来额外的成本,但是与此同时,您可以更有效地分配该主题领域内在的成本和折衷办法,而不必将其留给可以平衡此类成本和折衷办法的系统。 异步接口通常更准确地对应于物理发生在基本系统中的事件,因此,打开了更多的优化可能性。

例如,考虑到处理器和存储器系统的层次结构,为处理器和存储器系统提供了公平的基础结构,该基础结构负责在存储器中读取和写入数据。 在级别1(L1),高速缓存链接可能需要几纳秒,而内存链接本身必须一直经过L2,L3和主内存,这可能需要数百纳秒。 如果仅等待内存链接解析,则处理器将在相当长的时间内处于空闲状态。

认真的机制用于优化此类现象:以命令流的主导视图进行流水线处理,同时进行从内存和当前数据存储中提取的多种操作,分支预测并尝试进一步优化程序(即使程序跳转到另一个内存位置),对内存屏障的精确控制以确保所有这些复杂的机制将继续为更高级别的编程环境提供一致的内存模型。 所有这些事情都是为了优化性能并最大程度地利用硬件来在内存层次结构中隐藏10到100纳秒的延迟,并提供一个应该在其中执行同步执行的系统,同时仍然从处理器内核中压缩出不错的性能。

始终不清楚此类优化对特定代码段的有效性如何,并且经常需要用于分析性能的非常特定的工具来回答此问题。 在开发一些非常有价值的代码(例如,在Excel的转换引擎中,内核中的某些压缩选项或代码中的加密路径)中提供了此类分析工作。

具有较大延迟的操作(例如,从旋转磁盘读取数据)需要使用其他机制。 在这种情况下,当请求从OS磁盘读取时,有必要完全切换到另一个线程或进程,并且同步请求将保持未发送状态。 这样切换和支持这种机制的高成本是可以接受的,因为在这种情况下隐藏的等待时间可以达到几毫秒而不是纳秒。 请注意:这些成本并不仅限于在线程之间切换,还包括所有内存和资源的成本,实际上所有内存和资源都处于空闲状态,直到操作完成为止。 所有这些成本必须提供一个所谓的同步接口。

有许多根本原因,为什么可能有必要揭示系统中真正的基本异步性,对于这些异步性接口,最好是使用具有特定组件,级别或应用程序的异步接口,甚至考虑到直接应对日益增长的复杂性的需求。

并发 如果提供的资源是为真正的并行性设计的,则异步接口允许客户端更自然地同时发出多个请求并进行管理,以充分利用基本资源。

输送 。 减少某些接口上的实际延迟的通常方法是确保在任何给定时间等待发送多个请求(就性能而言,这实际上有用多少取决于我们从何处获取延迟源)。 在任何情况下,如果系统适用于流水线,则实际延迟可以减少等于等待发送的请求数量的因数。 因此,完成一个特定的请求可能需要10毫秒,但是如果您将10个请求写入管道,则响应可能每毫秒发生一次。 总吞吐量是可用流水线的函数,而不仅仅是每个请求的“通过”延迟。 发出请求并等待响应的同步接口将始终提供更高的端到端延迟。

包装(本地或远程) 。 异步接口更自然地在本地或远程资源上提供查询打包系统的实现(注意:在这种情况下,I / O接口另一端的“磁盘”可以是“远程”的)。 事实是应用程序应该已经可以接收响应,并且同时会有一些延迟,因为应用程序不会中断当前的处理。 这样的附加处理可以与自然会被捆绑的附加请求结合。

本地批处理可以更有效地传输一系列请求,甚至可以直接在本地计算机上压缩和删除重复的请求。 为了能够同时访问远程资源上的整个请求集,可能需要进行认真的优化。 一个典型的例子:磁盘控制器重新排序一系列读取和写入操作,以利用磁盘头在旋转板上的位置并最大程度地减少磁头的送入时间。 在以块级别运行的任何数据存储接口上,可以通过捆绑一系列查询(其中所有读取和写入操作都属于同一块)来严重提高性能。

当然,本地打包也可以在同步接口上实现,但是为此,您要么必须在很大程度上“隐藏事实”,要么将程序包捆绑作为接口的特殊功能,这可能会使整个客户端大大复杂化。 隐藏事实的一个经典示例是缓冲I / O。 应用程序调用“write(byte)” ,并且接口返回success ,但实际上,记录本身(以及有关是否成功传递的信息)在缓冲区被显式填充或为空之前不会发生,并且这种情况在关闭文件时发生。 许多应用程序可以忽略这些细节-只有在应用程序需要保证某些交互操作序列以及对较低级别正在发生的事情的真实了解时,才会发生混乱。

解锁/释放 。 在图形用户界面中,异步的最常见用法之一是防止阻止用户界面的主线程,以便用户可以继续与应用程序进行交互。 长期操作(例如网络通信)中的延迟不能隐藏在同步接口后面。 在这种情况下,用户界面线程必须显式管理此类异步操作,并应对程序中引入的其他复杂性。

用户界面就是这样一个示例,其中组件必​​须继续响应其他请求,因此不能依赖某种隐藏延迟的标准机制来简化程序员的工作。
通常,接收到套接字新连接的Web服务器组件将非常快速地将这样的连接转移到另一个在套接字上提供通信的异步组件,并且其本身将返回处理新请求的状态。

在同步模型中,组件及其处理模型通常紧密相关。
异步交互是一种经常用于松开绑定的机制。

降低成本和管理。 如上所述,隐藏异步的任何机制都涉及一些资源分配和开销。 对于特定的应用程序,这样的开销可能是不可接受的,并且该应用程序的设计者必须找到一种控制自然异步的方法。

一个有趣的例子是Web服务器的历史。 早期的Web服务器(基于Unix构建)通常使用单独的过程来管理传入的请求。 然后,此过程可以读取此连接并对其进行写入,实际上它是同步发生的。 这样的设计得以发展,并且在开始使用线程代替进程时降低了成本,但总体同步执行模型得以保留。 在现代设计方案中,已经认识到,不应主要关注计算模型,而应首先关注与数据库,文件系统交换信息或通过网络传输信息并制定响应时与读写有关的相关输入/输出。 。 通常,为此使用工作队列,其中允许对线程数进行一定的限制-在这种情况下,可以更清楚地构建资源管理。

NodeJS在后端开发中的成功不仅可以从众多JavaScript开发人员的支持中得到解释,这些JavaScript开发人员从小就创建了客户端Web界面。 在NodeJS中,就像在浏览器脚本中一样,要特别注意以异步方式进行设计,这与典型的服务器负载选项配合得很好:管理服务器资源主要取决于I / O,而不取决于处理。

还有一个有趣的方面:​​如果您坚持使用异步方法,则这种折衷方案将更加明确,并且由应用程序开发人员可以更好地进行调整。 在具有内存层次结构延迟的示例中,实际延迟(以处理器周期中的内存请求为准)在几十年中急剧增加。 处理器开发人员正努力添加新的缓存级别和其他机制,这些机制正日益推动处理器提供的内存模型,以便进一步保持同步处理的外观。

同步I / O边界处的上下文切换是另一个示例,其中实际的权衡随着时间发生了巨大变化。 处理器周期的增加快于与延迟的抗争,这意味着现在应用程序错过了更多的计算能力,而它以锁定形式处于空闲状态,等待IO的完成。 与折衷的相对成本有关的相同问题已导致OS设计人员坚持使用与早期模型更相似的内存管理方案,该方案具有进程交换(将整个进程映像完全加载到内存中,然后进程开始),而不是进行交换页面。 隐藏每页边框上可能出现的延迟太困难了。 大型顺序IO请求(与使用随机请求相比)显着提高了总吞吐量,这也正是这种变化的原因。

其他话题

取消

取消是一个复杂的话题 。 从历史上看,面向同步的系统在取消处理方面做得很差,有些甚至根本不支持取消。 取消实际上必须设计为“带外”,对于这种操作,需要调用单独的执行线程。 作为一种替代方法,异步模型是合适的,其中更自然地组织了对取消的支持,尤其是使用了这种简单的方法:它只是忽略最终返回哪个响应(以及是否返回)。 当延迟的可变性增加时,取消变得越来越重要,并且在实践中,错误率也会增加-这提供了一个很好的历史片段,证明了我们网络环境的发展。

节流/资源管理

根据定义,同步设计会产生一些限制,从而阻止应用程序在当前请求完成之前发出其他请求。 在异步设计中,节流不会一无是处,因此有时您必须明确实现它。 这篇文章以Word Web App为例说明这种情况,其中从同步设计到异步的过渡导致资源管理出现严重问题。 如果应用程序使用同步接口,则它很可能不会意识到节流已隐式嵌入到代码中。 删除此类隐式限制后,有可能(或有必要)更明确地组织资源管理。

在我职业生涯的初期,我们不得不将文本编辑器从Sun的同步图形API移植到X Windows时就不得不处理。 使用Sun API时,呈现操作是同步的,因此客户端直到完成后才获得控制权。 在X Windows中,图形请求是通过网络连接异步调度的,然后由显示服务器(可以在同一台或不同的计算机上)执行。

为了确保良好的交互性能,我们的应用程序应提供一些渲染(即,确保更新和渲染光标现在所在的行),然后检查是否需要读取其他键盘输入。如果找到了这样的输入,程序将退出渲染当前屏幕(在处理了当前队列中的输入后,它将以某种方式变得无关紧要)来读取和处理此输入,然后考虑最新的更改来重新绘制屏幕。这样的系统配置良好,可以与同步图形API一起使用。异步界面接收渲染请求的速度可能比执行请求的速度快,这是因为屏幕不断从用户输入中挂起。当交互式拉伸图像时,这变成了一个真正的噩梦,因为发出新请求的相对成本比实现它的成本低得多。UI严重挂起,在每次移动鼠标光标后执行一系列不必要的重绘。

, 30 (-, Facebook iPhone ). – ( , ), , . , , .



, . , Microsoft, , API – , , . , , – : «, !» , , .

, . – , . , : , , , . , - . , , async/await . «» , , , JavaScript. : , . Async/await , , . . , , , .

. , , . , , , . , , ( !). () , , .

, . , . async/await, , , , .

, , , – . , . – , , , ( , – Word Excel). , , - , , , .
, , , , .
, – . .

结论

. – , , . , , , . , ; , .

Source: https://habr.com/ru/post/zh-CN423573/


All Articles