首页 > 技术文章 > 协程 & IO模型 & HTTP协议

elijah-li 2022-01-18 21:56 原文

今日内容

  • 进程池与线程池的基本使用
  • 协程理论与实操
  • IO模型
  • 前端简介

内容详细

一、进程池与线程池的基本使用

1、进程池与线程池的作用

为了保证计算机硬件安全的前提下,提升程序的运行效率

2、回调机制

可以把提交任务方法 .submit() 的返回值(一个对象)赋值给 .add_done_call_back(call_back) 中的call_back 函数作为实参传入,在 call_back 函数中可以打印 .result() 同步提交方法的返回值,这样可以利用回调机制调用了同步操作 .result

3、使用流程

  • 导入线程池与进程池模块
  • 创建线程池(进程池)
  • 定义任务
  • 定义回调机制(打印 .result任务的返回值)
  • 朝线程池(进程池)中提交任务

4、实例

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os

# 创建线程池(进程池)
# pool = ProcessPoolExecutor() # 默认进程数为CPU总数
pool = ThreadPoolExecutor()  # 默认线程数是CPU总数乘以5

def task(n):
	print(n, os.getpid)
    time.sleep(1)
    return '结果:%s' % n**2  # 调用.result 的话任务需要有返回值

def call_back(ret):
    print('异步回调函数:%s' % res.result())

# 提交任务
for i in range(20):
    ret = pool.ubmit(task, i).add_done_callback(call_back)  # 异步提交
"""
同步:提交完任务之后原地等待任务的返回结果 期间不做任何事
异步:提交完任务之后不愿地等待任务的返回结果 结果由异步回调机制自动反馈
"""
# 等待线程池中所有的任务执行完毕之后 再获取各自任务的结果
# pool.shutdown()
# for i in obj_list:
#     print(i.result())  # 获取任务的执行结果  同步

在windows电脑中如果是进程池的使用也需要在__main__下面

二、协程理论与实操

1、简介

  • 进程: 资源单位
  • 线程: 工作单位
  • 协程: 程序员单方面意淫出来的名词,单线程下实现并发

欺骗CPU的行为:

单线程下外面如果能够自己检测出IO操作并且自己实现代码层面上的切换,那么对于 CPU 而言外面这个程序就没有 IO 操作,该线程就可以尽可能的占用 CPU

2、知识储备

# CPU被剥夺的条件
	1.程序长时间占用
	2.程序进入IO操作
# 并发
	切换+保存状态
  	以往学习的是:多个任务(进程、线程)来回切换

3、实例,代码实现协程

流程:

  • 导入固定模块 gevent 与 其内的 spawn类
  • 定义多个函数
  • 用 spawn 实例化协程对象(异步提交任务)
"""代码层面"""
第三方gevent模块:能够自主监测IO行为并切换
from gevent import monkey;monkey.patch_all()  # 固定代码格式加上之后才能检测所有的IO行为
from gevent import spawn
import time


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)


def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)


start = time.time()
# play('jason')  # 正常的同步调用
# eat('jason')  # 正常的同步调用
g1 = spawn(play, 'jason')  # 异步提交
g2 = spawn(eat, 'jason')  # 异步提交
g1.join()
g2.join()  # 等待被监测的任务运行完毕
print('主', time.time() - start)  # 单线程下实现并发,提升效率

4、协程实现TCP服务端并发效果

# 并发效果:一个服务端可以同时服务多个客户端
import socket
from gevent import monkey;monkey.patch_all()
from gevent import spawn
def talk(sock):
    while True:
        try:
            data = sock.recv(1024)
            if len(data) == 0:break
            print(data)
            sock.send(data+b'hello baby!')
        except ConnectionResetError as e:
            print(e)
            sock.close()
            break
def servers():
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen()
    while True:
        sock, addr = server.accept()
        spawn(talk,sock)
g1 = spawn(servers)
g1.join()
# 客户端开设几百个线程发消息即可
"""
最牛逼的情况:多进程下开设多线程 多线程下开设协程
	我们以后可能自己动手写的不多 一般都是使用别人封装好的模块或框架
"""

三、IO 模型

1、简介

"""理论为主 代码实现大部分为伪代码(没有实际含义 仅为验证参考)"""
IO模型研究的主要是网络IO(linux系统)

# 基本关键字
  同步(synchronous)	大部分情况下会采用缩写的形式  sync
  异步(asynchronous) async
  阻塞(blocking) 
  非阻塞(non-blocking)
 
# 研究的方向
Stevens在文章中一共比较了五种IO Model:
    * blocking IO           阻塞IO
    * nonblocking IO      	非阻塞IO
    * IO multiplexing      	IO多路复用
    * signal driven IO     	信号驱动IO
    * asynchronous IO    		异步IO
    由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model

2、四种 IO 模型

这些IO 模型主要是研究应用程序从内存中获取数据的阶段

阻塞 IO

最常见的 IO 模型,有两个等待阶段(wait for data , copy data)

image

非阻塞 IO

系统调用阶段变为非阻塞(轮询),有一个等待的阶段(copy data)

image

多路复用 IO

利用 select 或者 epoll 来监管多个程序,一旦某个程序需要的数据存在于内存中了,那么立刻通知该程序去取即可

image

异步 IO

只需要发起一次系统调用即可,之后不需要频繁发送,有结果并准备好之后会通过异步回调机制反馈给调用者

image

四、前端

1、简介

# 什么是前端
	任何与操作系统打交道的界面都可以称之为"前端"
  	手机界面(app) 电脑界面(软件) 平板界面(软件)
# 什么是后端
	不直接与用户打交道,而是控制核心逻辑的运行
  	各种编程语言编写的代码(python代码、java代码、c++代码)
"""全栈开发:前后端都会!!!
优势在于一个人干两个人的活 意味着工资也相当于两个人的薪资
缺陷在于一个人干两个人的活 有时候会忙不过来压力很大
"""
# 前端的学习思路
	声明:前端也是一门独立的学科 市面上也有前端工程师岗位,所以前端完整的课程内容也有解决六个半月,我们不可能全部学习,之学习最为核心最为基础的部门
  程度:掌握基本前端页面搭建 掌握前端后续内容的学习思路 看得懂前端工程师所编写的代码
  课程周期:5~7d
# 前端的学习流程
	前端三剑客
  	HTML		网页的骨架(没有样式很丑)
    CSS			网页的样式(给骨架美化)
    JS			网页的动态效果(丰富用户体验)

2、BS 架构

我们在编写TCP服务端的时候 针对客户端的选择可以是自己写的客户端代码也可以是浏览器充当客户端(bs架构本质也是cs架构)

我们自己编写的服务端发送的数据浏览器不识别 原因在于

  • 每个人服务端数据的格式千差万别 浏览器无法自动识别
  • 没有按照浏览器固定的格式发送
 	"""
 	浏览器可以访问很多服务端 如何做到无障碍的与这么多不同程序员开发的软件实现数据的交互
 		1.浏览器自身功能强大 自动识别并切换(太过消耗资源)
 		2.大家统一一个与浏览器交互的数据方式(统一思想)
 	"""

3、HTTP协议(重点)

协议:在实行数据通信前协定好的数据交互方式与其它事项
HTTP协议:浏览器(客户端)与服务端进行数据交互的协议
		服务端可以选择不遵守HTTP协议,但浏览器会无法识别响应过来的数据
    	需要再个客户端


# 四大特性
1、基于请求响应
	服务端不会主动给浏览器发送数据,一般是浏览器(客户端)先发送请求

2、基于TCP、IP 作用于应用层

3、无状态
	不会保存用户的状态信息
    "纵使见她千百遍 我都当她如初见"

4、无/短连接
	数据交互结束之后立刻断开传输的连接

# 请求响应格式
	请求格式:
    	请求首行(请求方式)
         请求头(一堆 K:V 键值对)
                (换行符,不能省略)
         请求体(部分请求方式没有请求体)

    响应格式:
		响应首行(响应状态码)
         响应头(一堆 K:v 键值对)
                (换行符,不能省略)
         响应体(服务器返回给浏览器的数据)

# 响应状态码
	用数字表示一些中文意思

1XX : 服务器接收到请求并正在处理,可以继续发送请求或者等待数据返回
2XX : 200 OK,响应成功,服务器放回数据
3XX : 重定向
4XX : 403 没有访问权限, 404 请求的资源不存在
5XX : 服务器端出现故障

4、HTML前戏

import socket


server = socket.socket()
server.bind(('127.0.0.1',8081))
server.listen()

"""
请求首行
b'GET / HTTP/1.1\r\n  
请求头
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"\r\n
sec-ch-ua-mobile: ?0\r\n
sec-ch-ua-platform: "macOS"\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
Sec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n
请求体(当前为空)
'
"""

while True:
    sock, addr = server.accept()
    while True:
        data = sock.recv(1024)
        if len(data) == 0:break
        print(data)
        # 遵循HTTP响应格式
        sock.send(b'HTTP/1.1 200 OK\r\n\r\n')
        sock.send(b'<h1>hello big baby<h1>')
        sock.send(b'<a href="https://www.jd.com">good see<a>')
        sock.send(b'<img src="https://imgcps.jd.com/ling4/100013209930/54iG5qy-55u06ZmN/6YOo5YiG5q-P5ruhMTk55YePMTAw/p-5bd8253082acdd181d02fa06/a677079b/cr/s/q.jpg"/>')
        # with open(r'data.txt','rb') as f:
        #     data = f.read()
        # sock.send(data)

推荐阅读