一.概述
Python 是解释型语言,在程序运行时边解释边执行代码。Python 会先将原生代码编译成字节码,运行过程中将字节码解释执行。Python 的运行环境需要 Python 虚拟机,翻译字节码执行。Python 代码会将字节码存放在 .pyc 的文件中,虚拟机会执行 .pyc 文件中的字节码。

相对于编译型语言,解释型语言在性能方面较差,为了提高性能,Python 也实现了进程和线程,但是由于各方面的限制,实现都不是很好。在 Python 中很少使用进程和线程,使用较多的是协程,协程的资源开销较少,并且易于控制。

二、函数的字节码
每一列的含义
源码行号 | 指令在函数中的偏移 | 指令符号 | 指令参数 | 实际参数值(参考)
image.png

三、socket
socket对外提供了一套丰富的接口,通过这些接口就可以统一、方便地使用 TCP/IP 协议。下面所列的是最常用的几个,我们在后面的学习中会用到它们。

  • socket 创建 socket 实例
  • bind 绑定地址和端口
  • listen 开始监听网络
  • accept 接收客户端连接
  • connect 连接服务器
  • send 发送数据
  • recv 接收数据
  • close 关闭 socket


image.png

# 服务器套接字相当于总机接线员,接到客户电话后转给分机客服
# 当成功接入一个客户端,accept 方法会返回一个临时套接字和对方地址 tcp_extension_sock, addr = tcp_server_sock.accept()

四、Linux I/O 模型
首先了解一下同步 (synchronous) 和异步 (asynchronous),阻塞 (blocking) 和非阻塞 (non-blocking) 之间的区别。

  • 同步意味着有序,多个程序之间协调一致,依次进行。
  • 异步意味着无序,多个程序执行顺序不确定,也可以交替运行。
  • 阻塞,一个程序在等待某个操作完成,自身停止运行,无法继续做其它任务,直到这个操作完成才继续运行。
  • 非阻塞,一个程序在等待某个操作完成,自身不会停止运行,继续执行其它任务。

阻塞/非阻塞和异步/同步是两组不同的概念,阻塞程序也可以实现异步执行,例如多线程执行。非阻塞也可以实现异步执行,例如多路复用。

I/O 指的是相对内存而言的 input 和 output。从文件、数据库、网络向内存中写入数据叫做 input;从内存向文件、数据库、网络中输出数据叫做 output 。Linux 系统 I/O 分为内核准备数据和将数据从内核拷贝到用户空间两个阶段。

Liunx 下有五种 I/O 模型:

  • 阻塞 I/O(blocking I/O)
  • 非阻塞 I/O (nonblocking I/O)
  • I/O 复用(I/O multiplexing)
  • 信号驱动 I/O (signal driven I/O (SIGIO))
  • 异步 I/O (asynchronous I/O)

本节课程首先粗略地介绍这五种模型,然后详细讲解其中的重点内容。

阻塞 I/O

当用户态进程调用系统函数获取数据时,如果内核中还没有准备好数据,用户态进程将一直等待,不进行其它操作,等内核将数据准备好之后,进程将数据从内核空间拷贝到用户空间,这时候系统调用函数返回,解除阻塞状态,处理接收到的数据。

Blocking

非阻塞 I/O

用户态进程调用系统函数获取数据时,如果内核中还没有准备好数据,会返回错误信息给进程,而进程接收到错误信息,不会阻塞。但是内核会继续准备数据,进程不知道内核什么时候会准备好数据,所以会不断调用系统函数询问内核,直到内核准备好数据,进程将数据从内核复制到用户空间,系统函数调用结束,进程开始处理接收到到数据。

Noblocking

I/O 复用

在计算机网络里面,有很多关于 “复用” 的用法,比如多路复用,意思就是本来一条链路上一次只能传输一个数据流,如果要实现两个源之间多条数据流同时传输,那就得需要多条链路了。复用技术可以通过将一条链路划分频率,或者划分传输的时间,使得一条链路上可以同时传输多条数据流。I/O 复用是在一个进程里会处理多个消息事件。I/O 多路复用技术实际使用得最多,接下来我们重点学习这种技术。

Multiplexing

信号驱动式 I/O 模型

当用户态进程需要数据时,会向内核发送一个信号,告诉内核要什么数据,然后去做自己其他的事情了,当内核态的数据准备好之后,内核马上给用户态进程发送信号,用户态进程收到信号立马调用系统调用,将数据从内核空间拷贝到用户空间,完成之后用户态进程开始处理接收到的数据。这种技术模型实际应用很少。

SigIO

异步 I/O 模型

用户态进程需要数据时会告诉内核态需要什么,然后就不用管了,可以做其它事情。内核会将用户态需要的数据准备好,并将数据复制到用户空间,这时才通知用户进程。用户进程可以直接处理用户空间的数据,无需等待任何 I/O 操作。

Asyncio

我们看到前四个 I/O 模型处理数据时,都是用户进程将数据从内核空间拷贝到用户空间,这一段时间对于进程来说是阻塞的。只有异步 I/O 是内核将数据从内核空间拷贝到用户空间,用户进程完全是异步操作,所以从内核层面看前四个模型可以称为同步 I/O ,最后一个是异步 I/O 。

五、IO多路复用
I/O 多路复用是一个线程里会监视多个文件描述符,在其中一个或者多个描述符准备好之后,内核会通知用户进程,用户进程来处理数据。

文件描述符(File Descriptor)是计算机科学中的术语,是一个用于描述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数,实际上它是一个指向 “文件打开记录表” 的索引值,“文件打开记录表” 是由内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层程序往往会围绕文件描述符展开编写工作。不过文件描述符这一概念只适用于类 Unix 操作系统。在一个进程中,每个套接字对应一个独一无二且固定不变的文件描述符。

和多线程、多进程相比,I/O 多路复用没有创建新的进程或线程,不必进行进程或线程的上下文切换,减小了系统开销。目前 Linux 系统中常见的几种 I/O 多路复用是 select 、poll 、epoll。

select 使用时间最早,也是支持系统最多的多路复用技术,跨平台性最好。select 维护了一个用来存放大量文件描述符的数据结构,通过设置或者检查数据结构的状态来处理数据。select 是最早出现的系统调用,它可以在大多数系统中使用。select.select 方法监视的文件描述符分为三类:writefds 可读、readfds 可写、和 exceptfds 异常。该方法调用后处于阻塞状态,直到有描述符就绪(有套接字可读、可写、异常)或者超时(timeout 参数指定等待时间)。select.select 方法的返回值是元组,元组中有三个列表,列表里的元素是描述符已就绪的套接字。

select 的缺点也是最多的:

  • 单个进程打开的文件描述符数量是有一定限制的,由 FD_SETSIZE 设置,在 32 位机默认是 1024 个,64 位机默认是 2048.
  • 对描述符进行轮询线性扫描,无论哪个文件描述符活跃了,都要将所有注册的描述符遍历一遍,效率低下。
  • 在用户空间和内核空间之间进行复制数据,系统开销大。

白风之下
10 声望3 粉丝

引用和评论

0 条评论