内容大纲
1、进程池与线程池
2、协程
3、gevent模块
4、协程实现服务端 客户端通信
5、IO模型
一、进程池与线程池
由于服务端需要处于24小时运行状态,可不能来一个用户就开一个进程,因为开线程/进程都是需要内存消耗的,一旦客户端访问量很多,那么服务端就会崩溃,我们就可以规定计算机最多可以创建多少进程/线程,虽然降低了效率,但是保证了服务端的稳定
进程池的使用 代码示例
# 进程池使用 代码示例
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time, os
# 实例化一个进程池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ProcessPoolExecutor(5) # 创建一个池子,池子里面有5个线程
def task(n):
print(n, os.getpid())
time.sleep(2)
return n ** 2
def fn_callback(future):
print('拿到了结果:%s' % future.result())
if __name__ == '__main__':
for i in range(10):
future = pool.submit(task, i).add_done_callback(fn_callback)
print('主')
主
0 917
1 918
2 919
3 920
4 921
5 917
拿到了结果:0
6 919
拿到了结果:4
7 920
拿到了结果:1
8 918
拿到了结果:9
9 921
拿到了结果:16
拿到了结果:25
拿到了结果:49
拿到了结果:36
拿到了结果:64
拿到了结果:81
线程池的使用 代码示例
# 线程池使用 代码示例
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time, os
# 实例化一个线程池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ThreadPoolExecutor(5) # 创建一个池子,池子里面有5个线程
# 将上面一行的代码在 ThreadPoolExecutor 与 ProcessPoolExecutor 中切换线程与进程池
def task(n):
print(n, os.getpid())
time.sleep(2)
return n ** 2
def fn_callback(future):
print('拿到了结果:%s' % future.result())
if __name__ == '__main__':
for i in range(10):
future = pool.submit(task, i).add_done_callback(fn_callback)
print('主')
0 933
1 933
2 933
3 933
4 933
主
拿到了结果:0
5 933
拿到了结果:1
6 933
拿到了结果:4
7 933
拿到了结果:9
8 933
拿到了结果:16
9 933
拿到了结果:25
拿到了结果:49
拿到了结果:36
拿到了结果:64
拿到了结果:81
二、协程
进程:资源单位
线程:执行单位
协程:单线程下时下并发(能够在多个任务之间切换和保存状态来节省IO)
协程原理:使用方法捕捉代码中的IO操作,在程序遇到IO的时候切换到线程的另一个方法中,目的是避免程序进入阻塞态而使CPU切换到别处
操作系统中的多道技术是在多个线程之间切换的
但是协程是在单个线程中的多个功能之间切换的
协程对于操作系统是不存在的东西,是程序员基于代码层面创建出来的方法
并不是 单个线程下实现 切换+保存状态(CPU切换) 就可以提升效率
如果没有IO操作的程序 使用协程反而会降低效率
结论:
将单个线程的效率提到最高,多进程下开多线程,多线程下用协程
这样就实现了高并发!
yield实现 保存上次运行状态 代码示例
# 首先看一下 串行执行的代码
import time
def func1():
for i in range(10000000):
i + 1
def func2():
for i in range(10000000):
i + 1
start = time.time()
func1()
func2()
stop = time.time()
print(stop - start) # 2.2284772396087646
# 基于yield实现并执行
import time
def func1():
while True:
10000000 + 1
yield
def func2():
g = func1() # 生成迭代器对象
for i in range(10000000):
# time.sleep(100): # 模拟IO,测试结果得:yield并不会捕捉到IO并自动切换
i + 1
next(g)
start = time.time()
func2()
stop = time.time()
print(stop - start) # 3.470855951309204
# yield能够实现保存上次运行状态,但是无法识别遇到IO才切换
三、gevent模块
from gevent import spawn
# spawn 就是帮我们管理任务的对象
# gevent模块不能识别其模块下之外的IO操作,但是其内部封装了一个monky模块,能够帮助我们识别其他所有的IO行为
# 代码示例:
from gevent import monkey;monkey.patch_all() # 检测所有的IO行为,写在代码的第一行
from gevent import spawn, joinall # joinall列表里面放多个对象,实现 一行代码join多个对象 的效果
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()
g1 = spawn(play, 'egon')
g2 = spawn(eat, 'egon')
joinall([g1, g2])
# 上一行代码等于:
# g1.join()
# g2.join()
print('主', time.time() - start)
egon play 1
egon eat 1
egon eat 2
egon play 2
主 5.0070531368255615
四、协程实现服务端 客户端 通信
服务端 与 客户端 通信 属于 IO密集型程序
服务端中的 连接 和 通信 都是 IO密集型 操作,我们只需要在这两者间切换就可以了,当一方遇到IO就切换到另一个上运行
# 代码示例:
# 服务端
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket
def communicate(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
def server(ip, port, backlog=5):
server.bind((ip, port))
server.listen(backlog)
while True: # 连接循环
conn, addr = server.accept()
print(addr)
# 通信
spawn(communicate, conn)
if __name__ == '__main__':
g1 = spawn(server, '127.0.0.1', 8080)
g1.join()
# 客户端
from threading import Thread, current_thread
import socket
def client():
client = socket.socket()
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
五、IO模型
1、阻塞IO
2、非阻塞IO
(服务端通信针对accept用s.setlocking(False)加异常捕获,CUP占用率过高)
3、IO多路复用
在只检测一个套接字的情况下,就能省去wait for data 过程
4、异步IO