非阻塞 I/O 下,数据在内核与用户空间的传递是否会导致休眠?
来源:stackoverflow
时间:2024-02-26 23:54:26 339浏览 收藏
编程并不是一个机械性的工作,而是需要有思考,有创新的工作,语法是固定的,但解决问题的思路则是依靠人的思维,这就需要我们坚持学习和更新自己的知识。今天golang学习网就整理分享《非阻塞 I/O 下,数据在内核与用户空间的传递是否会导致休眠?》,文章讲解的知识点主要包括,如果你对Golang方面的知识点感兴趣,就不要错过golang学习网,在这可以对大家的知识积累有所帮助,助力开发能力的提升。
我问这个问题是因为我正在研究 Go 中的多路 I/O,它使用 epollwait
。
当套接字准备好时,goroutine 将被唤醒并开始以非阻塞模式读取套接字。如果 read
系统调用在将数据从内核复制到用户的过程中仍然会被阻塞,我假设 gorouine 附加的内核线程也将进入睡眠状态。
我不太确定,如果我错了,希望有人能帮助纠正我。
解决方案
我无法完全解析你所写的内容。
我将尝试进行纯粹的猜测,并想象您可能正在监督 write(2)
和 read(2)
系统调用(以及类似的系统调用,例如 send(2)
和 recv( 2) 进入非阻塞模式的套接字上的
) 可以自由消耗(并分别返回)比请求更少的数据。
换句话说,对非阻塞套接字的 write(2)
调用被告知写入 1 MB 数据,将消耗与当前适合关联内核缓冲区的数据一样多的数据,并立即返回,表明它仅消耗同样多的数据。下一次立即调用 write(2)
可能会返回 EWOULDBLOCK
。
对于 read(2)
调用也是如此:如果您向它传递一个足够大的缓冲区以容纳 1 MB 数据,并告诉它读取该字节数,则该调用只会耗尽内核的内容缓冲并立即返回,表明它实际复制了多少数据。下一次立即调用 read(2)
可能会返回 EWOULDBLOCK
。
因此,任何获取数据或将数据放入套接字的尝试几乎都会立即成功:要么在数据被铲入内核缓冲区和用户空间之间之后,要么立即通过 EAGAIN
返回代码。
当然,据说操作系统线程有可能在执行此类系统调用的过程中被挂起,但这不算“在系统调用中阻塞。”
更新原始答案,以回应OP的以下评论:
<…>
这是我在书上看到的
《UNIX网络编程》(第一卷第3卷)第6.2章:
同步 I/O 操作导致请求进程 被阻塞,直到 I/O 操作完成。使用这些 定义,前四种 I/O 模型——阻塞、非阻塞、I/O 多路复用和信号驱动 I/O 都是同步的,因为 实际的 I/O 操作 (recvfrom) 会阻塞该进程。
它使用“块”来描述非阻塞 I/O 操作。这让我很困惑。
我仍然不明白如果进程实际上没有被阻止,为什么本书使用“阻止进程”。
我只能猜测,这本书的作者打算强调,从进入系统调用到从系统调用返回,该进程确实被阻止。读取和写入非阻塞套接字do block以在内核和用户空间之间传输数据(如果可用)。我们通俗地说这不会阻塞,因为我们的意思是“它不会阻塞等待并且在不确定的时间内不执行任何操作”。
本书的作者可能会将其与所谓的异步 I/O(在 Windows™ 上称为“重叠”)进行对比,在异步 I/O 中,您基本上为内核提供了一个包含数据的缓冲区,并要求它在以下情况下完全消除它:与您的代码并行——从某种意义上说,相关的系统调用立即返回,并且 I/O 在后台执行(相对于您的用户空间代码)。
据我所知,Go 在它支持的两个平台上都不使用内核的异步 I/O 设施。您可以查看 there,了解有关 Linux 及其当代版本 io_uring
subsystem 的发展。
哦,还有一点。本书可能(至少在叙述中)正在讨论一种简化的“经典”方案,其中没有进程内线程,唯一的并发单元是进程(具有单个执行线程)。在这个方案中,任何系统调用显然都会阻塞整个过程。相比之下,Go 仅在支持线程的内核上工作,因此在 Go 程序中,系统调用永远不会阻塞整个进程 - 只会阻塞它所调用的线程。
让我再次尝试解释这个问题,正如我认为的 OP 所说的那样。
服务多个客户端请求的问题并不新鲜——最明显的第一个语句是 "The C10k problem"。
快速回顾一下,在其管理的套接字上进行阻塞操作的单线程服务器实际上一次只能处理一个客户端。
为了解决这个问题,有两种简单的方法:
- 分叉服务器进程的副本来处理每个传入的客户端连接。
- 在支持线程的操作系统上,在同一进程内分叉一个新线程来处理每个传入的客户端。
它们各有优缺点,但它们在资源使用方面都很糟糕,而且更重要的是,它们不能很好地适应大多数客户端执行的 I/O 速率和带宽相对较低的事实典型服务器上可用的处理资源。
换句话说,当与客户端进行典型的 TCP/IP 交换时,服务线程大部分时间都在客户端套接字上的 write(2)
和 read(2)
调用中休眠。
这就是大多数人在谈论套接字上的“阻塞操作”时的意思:如果一个套接字是阻塞的,那么对其进行的操作将阻塞,直到它实际上可以执行,并且原始线程将被放入睡眠不确定的时间。
另一个需要注意的重要事项是,当套接字准备就绪时,与唤醒之间的睡眠时间相比,完成的工作量通常是微乎其微的。 当踏板休眠时,其资源(例如内存)实际上被浪费了,因为它们不能用于执行任何其他工作。
输入“投票”。它通过注意到网络套接字的就绪点相对较少且相差很远来解决资源浪费的问题,因此让单个线程服务大量此类套接字是有意义的:它允许将线程保持在几乎相同的状态。理论上尽可能繁忙,并且还允许在需要时进行横向扩展:如果单个线程无法应对数据流,则添加另一个线程,等等。
这种方法确实很酷,但它有一个缺点:必须重写读取和写入数据的代码以使用回调样式而不是原始的简单顺序样式。使用回调进行编写很困难:您通常必须实现复杂的缓冲区管理和状态机来处理这个问题。
Go 运行时通过为其执行流程单元添加另一层调度来解决这个问题——goroutines:对于 goroutine 来说,socket 上的操作总是阻塞的,但是当 goroutine 即将在 socket 上阻塞时,这会通过仅挂起来透明地处理goroutine 本身——直到请求的操作能够继续——并使用 goroutine 运行的线程来执行其他工作。
这样可以充分利用两种方法的优点:程序员可以轻松编写经典的连续无回调网络代码,但用于处理网络请求的线程得到充分利用²。
对于最初的阻塞问题,当套接字上的数据传输发生时,goroutine 及其运行的线程确实会被阻塞,但由于发生的是内核和用户空间缓冲区之间的数据铲除,大多数时候延迟都很小,与经典的“轮询”情况没有什么不同。
请注意,在 Go 中执行系统调用(包括不可轮询描述符上的 I/O)(至少直到 Go 1.14,包括 Go 1.14)确实会阻塞调用 goroutine 及其运行的线程on,但处理方式与可轮询描述符的处理方式不同:当一个特殊的监控线程注意到一个 goroutine 在系统调用中花费的时间超过一定的时间(20 µs,IIRC)时,运行时会拉动所谓的“处理器”(一个运行时)在 goroutine 下运行 goroutine(在 OS 线程上)并尝试使其在另一个 OS 线程上运行另一个 goroutine 的东西;如果有一个 goroutine 想要运行但没有空闲的操作系统线程,Go 运行时会创建另一个 goroutine。
因此,“正常”阻塞 I/O 在 Go 中仍然处于阻塞状态:它会阻塞 goroutine 和操作系统线程,但 Go 调度程序确保整个程序仍然能够取得进展。
这可以说是使用内核提供的真正异步 I/O 的完美案例,但目前还没有实现。
¹ 请参阅 this classic essay 了解更多信息。
² Go 运行时当然不是第一个率先提出这一想法的。例如,看看 the State Threads library(以及最近的 libtask
),它在纯 C 中实现了相同的方法; ST 库有出色的文档解释了这个想法。
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《非阻塞 I/O 下,数据在内核与用户空间的传递是否会导致休眠?》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习