参考文档:https://zhuanlan.zhihu.com/p/63179839
阻塞是进程调度的关键一环,指的是进程在等待某事件(如接收到网络数据)发生之前的等待状态,recv、select和epoll都是阻塞方法。
如下面所示一段服务端server的代码:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 6999)) # 绑定要监听的端口
server.listen(5)
while True:
conn, addr = server.accept() # 等待链接
print(conn, addr)
while True:
try:
data = conn.recv(1024) # 接收数据
print('recive:', data) # 打印接收到的数据
conn.send(data.upper()) # 然后再发送数据
except Exception as e:
print('关闭了正在占线的链接!')
break
conn.close()复制
如上面代码片段中的 data = conn.recv(1024),recv是个阻塞方法,当程序运行到recv时,它会一直等待,直到接收到数据才往下执行。
那么阻塞的原理是什么?我们知道进程状态转换有如下图所示的流程,正在运行的进程由于提出系统服务请求(如I/O操作),但因为某种原因未得到操作系统的立即响应,或者需要从其他合作进程获得的数据尚未到达等原因,该进程只能调用阻塞原语把自己阻塞,等待相应的事件出现后才被唤醒。那么更详细的解释是什么呢?
工作队列
操作系统为了支持多任务,实现了进程调度的功能,会把进程分为“运行”和“等待”等几种状态。运行状态是进程获得cpu使用权,正在执行代码的状态;等待状态是阻塞状态,比如上述程序运行到recv时,程序会从运行状态变为等待状态,接收到数据后又变回运行状态。操作系统会分时执行各个运行状态的进程,由于速度很快,看上去就像是同时执行多个任务。
下图中的计算机中运行着A、B、C三个进程,其中进程A执行着上述基础网络程序,一开始,这3个进程都被操作系统的工作队列所引用,处于运行状态,会分时执行。

等待队列
当进程A执行到创建socket的语句时,操作系统会创建一个由文件系统管理的socket对象(如下图)。这个socket对象包含了发送缓冲区、接收缓冲区、等待队列等成员。等待队列是个非常重要的结构,它指向所有需要等待该socket事件的进程。

当程序执行到recv时,操作系统会将进程A从工作队列移动到该socket的等待队列中(如下图)。由于工作队列只剩下了进程B和C,依据进程调度,cpu会轮流执行这两个进程的程序,不会执行进程A的程序。所以进程A被阻塞,不会往下执行代码,也不会占用cpu资源。

唤醒进程
当socket接收到数据后,操作系统将该socket等待队列上的进程重新放回到工作队列,该进程变成运行状态,继续执行代码。也由于socket的接收缓冲区已经有了数据,recv可以返回接收到的数据。
注:这是以前我发表在其他平台的文章,最近决定以后主要在公众号更新,所以准备把之前其他平台发表的文章移到这边来,便于归档和查找。祝大家安好!
点个“赞 or 在看” 你最好看!
👇典典下面的小咔片给作者鼓励下吧,这对我很重要🐶
评论
