linux 内核 睡眠,linux 在将数据从内核复制到用户期间,非阻塞I / O是否会进入睡眠状态? - 糯米PHP...

我无法完全解析您所写的内容。

我将尝试做出一个纯粹的猜测,并且可以想到,您可能忽略了以下事实:处于非阻塞模式的套接字上的write(2)and和read(2)syscall(以及它们的同类调用,例如send(2)andrecv(2))可以自由使用(并分别返回)少于要求的数据。换句话说,在非阻塞套接字上被调用写入1兆字节数据的调用将消耗当前与关联的内核缓冲区匹配的数据,并立即返回,表明它仅消耗相同的数据。下一次立即致电可能会返回。

write(2)write(2)EWOULDBLOCK

这同样适用于该read(2)呼叫:如果你传递一个缓冲区大到足以容纳1兆字节的数据,并告诉它读取的字节数,呼叫只会耗尽的内容内核缓冲区并立即返回,标志着多少实际复制的数据。下一次立即致电read(2)可能会返回EWOULDBLOCK。

因此,任何将数据获取或放入套接字的尝试都几乎立即成功:将数据铲入内核缓冲区和用户空间之间之后,或者立即使用EAGAIN返回代码。

当然,还有据说是OS线程在执行这样的系统调用的中间偏右的暂停的可能性,但是这并不算作“堵在一个系统调用。”

根据OP的以下评论更新为原始答案:

这就是我在“ UNIX网络编程”(第1卷,第3卷)的第6.2章中看到的内容:

同步I / O操作导致请求过程被阻塞,直到该I / O操作完成。使用这些定义,前四个I / O模型(阻塞,非阻塞,I / O多路复用和信号驱动的I / O)都是同步的,因为实际的I / O操作(recvfrom)会阻塞该过程。

它使用“块”来描述非阻塞I / O操作。这让我感到困惑。

我仍然不明白,如果实际上没有阻止该过程,那么本书为什么使用“阻止该过程”。

我只能猜测,这本书的作者打算强调指出,自从进入syscall并从其返回之前,该过程确实被阻塞。对非阻塞套接字的读取和写入会阻塞以在内核和用户空间之间传输数据(如果可用)。我们口语地说这不会阻止,因为我们的意思是“它不会阻止等待并在不确定的时间内不执行任何操作”。

本书的作者可能将其与所谓的异步I / O(在Windows™上称为“重叠”)进行对比,在异步I / O中,您基本上为内核提供了一个用于/用于数据的缓冲区,并要求它与您的并行处理完全消除。代码-从某种意义上讲,相关的syscall立即返回,并且I / O在后台执行(关于您的用户空间代码)。

据我所知,Go在它所支持的平台上都没有使用内核的异步I / O工具。你可能看起来没有对有关Linux及其在当代的发展io_uring子系统。

哦,还有一点。这本书可能(至少到那时为止是在叙述中)正在讨论一种简化的“经典”方案,其中没有进程内线程,并且并发的唯一单元是进程(具有单个执行线程)。在这种方案中,任何系统调用显然都会阻塞整个过程。相比之下,Go仅在支持线程的内核上工作,因此在Go程序中,系统调用永远不会阻塞整个进程-仅阻止它被调用的线程。

让我再一次尝试解释问题,正如我所知,OP指出了这一点。

服务多个客户端请求的问题并不是新问题,它最明显的第一句话就是“ C10k问题”。

为了快速重述,在其管理的套接字上具有阻塞操作的单线程服务器实际上只能一次处理一个客户端。

为了解决这个问题,有两种简单的方法:

分叉服务器进程的副本以处理每个传入的客户端连接。

在支持线程的操作系统上,在同一进程内派生一个新线程来处理每个传入的客户端。

它们各有利弊,但都在资源使用方面很糟糕,而且更重要的是,它们与大多数客户端在处理过程中执行的I / O速率和带宽相对较低的事实相矛盾。典型服务器上可用的资源。

换句话说,当与客户端进行典型的TCP / IP交换服务时,服务线程大部分时间都处于睡眠状态,write(2)并read(2)在客户端套接字上进行调用。

这就是大多数人在谈论套接字上的“阻塞操作”时的意思:如果套接字正在阻塞,并且对该套接字的操作将一直阻塞,直到可以实际执行为止,并且始发线程将进入休眠状态并不确定的数量。时间。

另一个要注意的重要事项是,当套接字准备就绪时,与唤醒之间的睡眠时间相比,完成的工作量通常很小。胎面睡觉时,其资源(例如内存)被有效地浪费了,因为它们不能用于执行任何其他工作。

输入“轮询”。通过注意到网络套接字的就绪点相对稀少并且介于两者之间,它解决了资源浪费的问题,因此,有很多这样的套接字由单个线程提供服务是有意义的:它允许将线程保持为从理论上讲尽可能忙,并且还可以在需要时进行扩展:如果单个线程无法处理数据流,请添加另一个线程,依此类推。

这种方法肯定很酷,但是它有一个缺点:读取和写入数据的代码必须重新编写以使用回调样式,而不是原始的普通顺序样式。用回调编写是很难的:通常,您必须实现复杂的缓冲区管理和状态机来处理此问题。

Go运行时通过为其执行流程单元添加另一层调度来解决此问题,即goroutines:对于goroutine,套接字上的操作始终处于阻塞状态,但是当goroutine即将在套接字上阻塞时,这将通过仅挂起进行透明处理goroutine本身-直到请求的操作将能够继续进行-并使用goroutine正在运行的线程来执行其他工作¹。

这样可以兼具两种方法的优点:程序员可以编写经典的无脑顺序无回调的网络代码,但是用于处理网络请求的线程已被充分利用²。

至于最初的阻塞问题,当套接字上的数据传输发生时,goroutine及其运行的线程确实都被阻塞了,但是由于发生的是内核与用户空间缓冲区之间的数据交换,因此延迟是大部分时间都很小,与经典的“轮询”案例没有什么不同。

请注意,在Go中执行系统调用(包括对非轮询描述符执行I / O操作)(至少直到Go 1.14,包括Go 1.14)确实会阻止调用goroutine及其运行的线程,但处理方式与pollable有所不同描述符:当一个特殊的监视线程注意到在某个系统调用中花费的goroutine超过一定的时间量(20 µs,IIRC)时,运行时就会从下方拉出所谓的“处理器”(在OS线程上运行goroutine的运行时事物) gorotuine并尝试使其在另一个OS线程上运行另一个goroutine;如果有一个想运行的goroutine,但没有空闲的OS线程,则Go运行时会创建另一个。

因此,“正常”阻塞I / O仍然在Go中处于两种阻塞状态:它同时阻塞goroutine和OS线程,但是Go调度程序确保整个程序仍然能够进行。

对于使用内核提供的真正异步I / O,这可以说是一个完美的案例,但是还不存在。

¹有关更多信息,请参见此经典文章。

²Go运行时肯定不是第一个倡导此想法的人。例如,看一下在普通C中实现相同方法的State Threads库(以及更新的libtask)。ST库中有出色的文档,可以解释这个想法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值