1. 进程池
1.1 简介
进程池是资源进程、管理进程组成的技术的应用.
进程池技术的应用至少由以下两部分组成:
资源进程:
预先创建好的空闲进程,管理进程会把工作分发到空闲进程来处理。
管理进程:
管理进程负责创建资源进程,把工作交给空闲资源进程处理,回收已经处理完工作的资源进程。
可以通过IPC,信号,信号量,消息队列,管道... 让资源进程与管理进程进行交互.
1.2 为什么要有进程池?
应用程序运行忙时会有成千上万的任务需要被执行, 闲时可能只有零星任务.
降低程序的效率低的两个问题:
* 1. 每个进程创建需要消耗时间,销毁进程也需要消耗时间.
* 2. 那么在成千上万个任务需要被执行的时候,
也不能无限制的根据任务去开启或者结束进程,
操作系统不可能让他们同时执行.
使用进程池解决上述问题.
定义一个池子, 在池子中创建固定数量的进程数.
有需求来了,就拿一个池中的进程来处理任务,
等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务.
如果有很多任务需要执行,池中的进程数量不够,
任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。
进池中进程的数量是固定的, 同时也控制了同一时间最多可以运行的进程数据.
这样不会增加操作系统的调度难度, 还节省了开关进程的时间,
也一定程度上能够实现并发效果。
2. Pool模块开启进程池
通过维护一个进程池来控制进程的数目,
比如使用httpd的进程模式, 可以规定最大进程数和最小进程数,
2.1 开启进程池
Python的multiprocessing模块Pool类提供开启进程池的方法.
线程池对象 = Pool([numprocess],[initializer],[initargs])
参数:
numprocess 要创建的进程数,如果省略,将默认使用cpu_count()的值
initializer 每个工作进程启动时要执行的可调用对象,默认为None
initargs 传给initializer的参数组
2.2 常用方法
同步调用:
.apply(func,args,kwargs) 为进程池对象绑定函数.
func: 指定执行的函数.
args,kwargs: 传递的参数
异步调用:
.apply_async(func,args,kwargs) 为进程池对象绑定函数.
func: 指定执行的函数.
args,kwargs: 传递的参数
然后返回结果, 结果是AsyncResult类的实例, 可以使用回调函数callback,
将前面func返回的结果单做参数传给回调函数.
p.close( ) 关闭进程池,防止进一步操作, 如果所有操作持续挂起,它们将在工作进程终止前完成
P.jion() 等待所有工作进程退出。此方法只能在close()或teminate()之后调用, 否则报错.
2.3 同步调用示例
同步:提交完任务之后原地等待任务的返回结果, 期间不做任何事.
异步:提交完任务之后不愿地等待任务的返回结果 结果由异步回调机制自动反馈.
import os
# 获取逻辑内核(一个线程算一个cpu)
cpu_num = os.cpu_count() # 20 12700h 14核20线程
# 安装psutil模块: pip install psutil
import psutil
# False 获取物理内核, 为True 获取逻辑内核
cpu_num = psutil.cpu_count(logical=False)
print(cpu_num) # 14
from multiprocessing import Pool
import time, os
def func(i):
# 获取进程号
print(f"子进程:{os.getpid()}")
# 延时1秒
time.sleep(1)
# 返回值
return i
if __name__ == '__main__':
# 创建进程池并开启进程
p = Pool(processes=4)
# 定义一个空列表
res_list = []
# 计时开启
start_time = time.time()
for i in range(10):
# 创建十个个任务, 使用同步调用的方式, 会等待返回结果
res = p.apply(func, args=(i,))
# 接受返回值
res_list.append(res)
# 先关闭进程池, 不会再有新的进程加入到pool中, 防止进一步的操作(同步调用可以不加此方法)
p.close()
# 必须在close调用之后执行, 否则报错, 执行后等待所有子进程结束(同步调用可以不加此方法)
p.join()
# 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
print(res_list)
# 程序执行时间
print(f'程序执行时间:{time.time() - start_time}秒')
终端显示:
子进程:6228
子进程:4904
子进程:9688
子进程:10540
子进程:6228
子进程:4904
子进程:9688
子进程:10540
子进程:6228
子进程:4904
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:10.156244277954102秒
________________________________________________________________________
从pid可以看出始终使用的是那几个进程.
同步提交需要等待上一个任务结束拿到结果才能进行下一个任务, 所以用时 10 秒多一点.
2.4 异步调用示例
from multiprocessing import Pool
import time, os
# 定义进程调用的函数
def func(i):
# 获取进程号
print(f"子进程:{os.getpid()}")
# 延时1秒
time.sleep(1)
# 返回值
return i
if __name__ == '__main__':
# 本机CPU个数
cpu_num = os.cpu_count() # 20
# 创建进程池并开启进程
p = Pool(processes=cpu_num)
# 定义一个空列表
res_list = []
# 计时开启
start_time = time.time()
for i in range(cpu_num):
# 创建二十个任务, 使用同步调用的方式, 会等待返回结果
res = p.apply_async(func, args=(i,))
# 接受返回值
res_list.append(res)
# 先关闭进程池, 不会再有新的进程加入到pool中, 防止进一步的操作(同步调用可以不加此方法)
p.close()
# 必须在close调用之后执行, 否则报错, 执行后等待所有子进程结束(同步调用可以不加此方法)
p.join()
# 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
print([i.get() for i in res_list])
# 程序执行时间
print(f'程序执行时间:{time.time() - start_time}秒')
终端显示:
子进程:7980
....
子进程:15240
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
程序执行时间:1.1466412544250488秒
___________________________________________________________________________________
异步调用方式, 如果任务进行时遇到阻塞操作会, 会切换进程, 等到阻塞结束在进入就绪态等待执行...
返回值是AsyncResul 的对象, 需要使用 .get() 方法取值.
3. 回调函数
回调函数: 将一个函数的内存地址作为参数传给另一个函数处理, 这一个函数就称为回调函数.
当进程池中一个异步任务处理完之后, 它去通知主进程自己结束了,
让主进程处调用另一个函数去处理该结果.
from multiprocessing import Pool
# 进程调用方法
def add_num(n):
for i in range(100000000):
n += i
return n # 返回任务执行的结果
# 回调函数
def callback(num):
print(num + 1)
if __name__ == '__main__':
# 开启进程池
p = Pool(3)
li = []
create_time = time.time()
for i in range(3):
# 异步调用,并使用callback设置回调
res = p.apply_async(add_num, args=(i,), callback=callback)
li.append(res)
# 关闭进程池
p.close()
# 等待子进程结束
p.join()
# 使用get方法拿到结果是一个迭代器, 使用[]显示值
print([ii.get() for ii in li])
终端显示:
4999999950000002
4999999950000001
4999999950000003
[4999999950000000, 4999999950000001, 4999999950000002]
应用场景:
我们可以将耗时间或者阻塞的代码作为一个任务放入进程池, 在主进程中指定回调函数,
当子进程任务执行完毕后, 返回结果交给主进程去调用函对结果进行处理.
这样主进程在执行通过回调函数省去了I/O的过程, 直接拿到的就是任务的结果.
4. ThreadPool开启线程池
线程池的使用与进程池一样.
使用ThreadPool类生线程池.
from multiprocessing.pool import ThreadPool
4.1 同步调用
# 导入线程池
from multiprocessing.pool import ThreadPool
import time, os
def func(i):
# 获取进程号
print(f"进程号:{os.getpid()}")
# 延时1秒
time.sleep(1)
# 返回值
return i
if __name__ == '__main__':
# 创建线程池并开启线程
p = ThreadPool(processes=4)
# 定义一个空列表
res_list = []
# 计时开启
start_time = time.time()
for i in range(10):
# 创建十个个任务, 使用同步调用的方式, 会等待返回结果
res = p.apply(func, args=(i,))
# 接受返回值
res_list.append(res)
# 先关闭线程池, 不会再有新的线程加入到pool中, 防止线一步的操作(同步调用可以不加此方法)
p.close()
# 必须在close调用之后执行, 否则报错, 执行后等待所有子线程结束(同步调用可以不加此方法)
p.join()
# 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
print(res_list)
# 程序执行时间
print(f'程序执行时间:{time.time() - start_time}秒')
终端显示:
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
进程号:4388
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:10.074846267700195秒
4.2 异步调用
from multiprocessing.pool import ThreadPool
import time, os
# 定义线程调用的函数
def func(i):
# 获取线程号
print(f"子线程:{os.getpid()}")
# 延时1秒
time.sleep(1)
# 返回值
return i
if __name__ == '__main__':
# 本机CPU个数
cpu_num = os.cpu_count() # 20
# 创建线程池并开启线程
p = ThreadPool(processes=cpu_num)
# 定义一个空列表
res_list = []
# 计时开启
start_time = time.time()
for i in range(cpu_num):
# 创建二十个任务, 使用同步调用的方式, 会等待返回结果
res = p.apply_async(func, args=(i,))
# 接受返回值
res_list.append(res)
# 先关闭线程池, 不会再有新的线程加入到pool中, 防止线一步的操作(同步调用可以不加此方法)
p.close()
# 必须在close调用之后执行, 否则报错, 执行后等待所有子线程结束(同步调用可以不加此方法)
p.join()
# 同步调用, 得到的就是最终结果,(异步调用得到的是对象, 需要使用get方法取值)
print([i.get() for i in res_list])
# 程序执行时间
print(f'程序执行时间:{time.time() - start_time}秒')
终端显示:(挤在一行)
进程号:17736进程号:17736
...
进程号:17736
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
程序执行时间:1.0043601989746094秒
5. ProcessPoolExecutor开启进程池
导入ProcessPoolExecutor模块:
from concurrent.futures import ProcessPoolExecutor
开启进程池:
进程池对象 = ProcessPoolExecutor(开启的数量)
进程池对象常用方法:
res = pool.submit(调用的函数, 函数的参数) 默认接受两个参数.
res是一个Future对象, 异步接收子进程调用的函数的返回值.
Future对象.result() 获取返回值.
pool.shutdown() 等待子进程执行完毕
回调函数使用:
pool.submit().add_done_callback(回调函数名)
5.1 同步提交
from concurrent.futures import ProcessPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取进程pid
print(n, os.getpid())
time.sleep(2)
# 返回结果
return n
if __name__ == '__main__':
# 开设进程池
pool = ProcessPoolExecutor(5)
res_list = []
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回一个<class 'concurrent.futures._base.Future'>对象
res = pool.submit(task, i)
# 对象使用.result()获取值
res_list.append(res.result()) # 这一步是区分程序是同步执行还是异步执行的关键.
"""
获取子进程后调用函数后, 返回值会异步封装到Future对象中
执行到res.result(), res的值还没有.result()会等待res有值才会执行
程序就进入阻塞, 影响了开启后续的子进程, 程序就是同步的了.
"""
# 等待子进程执行完毕
pool.shutdown()
print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 10012
0
1 20360
1
2 4864
2
3 13272
3
4 9252
4
5 10012
5
6 20360
6
7 4864
7
8 13272
8
9 9252
9
程序执行时间:20.1526141166687
5.2 异步提交
from concurrent.futures import ProcessPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取进程pid
print(n, os.getpid())
time.sleep(2)
# 返回结果
return n
if __name__ == '__main__':
# 开设进程池
pool = ProcessPoolExecutor(5)
# 创建一个空列表
res_list = []
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回一个<class 'concurrent.futures._base.Future'>对象
res = pool.submit(task, i)
# 将返回结果添加到列表中
res_list.append(res)
"""
获取子进程后调用函数后, 返回值会异步封装到Future对象中
执行到res_list.append(res) 将<class 'concurrent.futures._base.Future'>对象添加到列表中
在主进程中获取Future对象的值, 不影响连续开启子进程. 程序就是异步的了.
"""
# 等待子进程执行完毕
pool.shutdown()
# 获取返回结果
print([res.result() for res in res_list])
print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 13108
1 17476
2 17544
3 4916
4 1964
5 17476
6 13108
7 1964
8 4916
9 17544
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:4.105138063430786
________________________________________________________________________
从pid可以看出始终使用的是那几个进程.
同步提交需要等待上一个任务结束拿到结果才能进行下一个任务, 所以用时 4 秒多一点.
5.3 回调函数
from concurrent.futures import ProcessPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取进程pid
print(n, os.getpid())
time.sleep(2)
# 返回结果
return n
# 定义回调函数
def callback(x):
print(f'回调函数处理结果: {x.result()}')
if __name__ == '__main__':
# 开设进程池
pool = ProcessPoolExecutor(5)
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回值会作为参数传递给回调函数
pool.submit(task, i).add_done_callback(callback)
# 等待子进程执行完毕
pool.shutdown()
print(f'程序执行时间:{time.time() - create_time}')
终端显示
0 14240
1 6760
2 17752
3 6044
4 8672
5 14240
回调函数处理结果: 0 # 子进程执行完毕后自动回调
6 8672
7 6044
8 17752
回调函数处理结果: 4
9 6760
回调函数处理结果: 3
回调函数处理结果: 2
回调函数处理结果: 1
回调函数处理结果: 5
回调函数处理结果: 8
回调函数处理结果: 9
回调函数处理结果: 7
回调函数处理结果: 6
程序执行时间:4.103053569793701
6. ThreadPoolExecutor开启线程池
使用方式与进程池一样.
6.1 同步提交
from concurrent.futures import ThreadPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取线程pid
print(f'{n} {os.getpid()}\n', end='')
time.sleep(2)
# 返回结果
return n
if __name__ == '__main__':
# 开设线程池
pool = ThreadPoolExecutor(5)
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回值会作为参数传递给回调函数
res = pool.submit(task, i)
# 获取返回值
print(res.result())
# 等待线程执行完毕
pool.shutdown()
print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 1384
0
1 1384
1
2 1384
2
3 1384
3
4 1384
4
5 1384
5
6 1384
6
7 1384
7
8 1384
8
9 1384
9
程序执行时间:20.06202530860901
6.2 异步提交
from concurrent.futures import ThreadPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取线程pid
print(f'{n} {os.getpid()}\n', end='')
time.sleep(2)
# 返回结果
return n
if __name__ == '__main__':
# 开设线程池
pool = ThreadPoolExecutor(5)
res_list = []
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回值会作为参数传递给回调函数
res = pool.submit(task, i)
res_list.append(res)
# 等待线程执行完毕
pool.shutdown()
print([res.result() for res in res_list])
print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 9644
1 9644
2 9644
3 9644
4 9644
5 9644
6 9644
7 9644
8 9644
9 9644
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
程序执行时间:4.025704383850098
6.3 回调函数
from concurrent.futures import ThreadPoolExecutor
import time
import os
# 定义一个任务
def task(n):
# 获取线程pid
print(f'{n} {os.getpid()}\n', end='')
time.sleep(2)
# 返回结果
return n
# 定义回调函数
def callback(x):
print(f'回调函数处理结果: {x.result()}\n', end='')
if __name__ == '__main__':
# 开设线程池
pool = ThreadPoolExecutor(5)
create_time = time.time()
for i in range(10):
# 提交任务与任务参数, 返回值会作为参数传递给回调函数
pool.submit(task, i).add_done_callback(callback)
# 等待线程执行完毕
pool.shutdown()
print(f'程序执行时间:{time.time() - create_time}')
终端显示:
0 17448
1 17448
2 17448
3 17448
4 17448
回调函数处理结果: 3
5 17448
回调函数处理结果: 4
回调函数处理结果: 0
6 17448
回调函数处理结果: 2
7 17448
8 17448
回调函数处理结果: 1
9 17448
回调函数处理结果: 9
回调函数处理结果: 7
回调函数处理结果: 6
回调函数处理结果: 5
回调函数处理结果: 8
程序执行时间:4.017932891845703
6. 携程
6.1 简介
协程(微线程,纤程,也称为用户级线程) :在不开辟线程的基础上完成多任务,
也就是在单线程的情况下完成多任务,多个任务之间遇到io阻塞就会切换任务执行.
6.2 原理
CPU被剥夺的条件:
* 1. 程序长时间占用
* 2. 程序进入IO操作(欺骗CPU)
单线程下自己检测IO操作并且实现代码层面的任务切换
那么对于CPU而言这个程序就没有IO操作, CPU会尽可能的被占用.
6.3 使用
使用gevent第三方模块对脚本文件进行检测, 遇到io切行任务.
# 执行两个函数
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('kid')
eat('qq')
# 串行执行
print('主', time.time() - start)
终端显示:
kid play 1
kid play 2
qq eat 1
qq eat 2
主 8.015759944915771
# 携程执行两个函数
from gevent import monkey
from gevent import spawn
import time
# 检测所有的IO行为
monkey.patch_all()
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, 'kid')
# 异步提交
g2 = spawn(eat, 'qq')
# 等待被监测的任务运行完毕
g1.join()
g2.join()
# 单线程下实现并发,提升效率
print('主', time.time() - start)
终端显示:
kid play 1
qq eat 1
qq eat 2
kid play 2
主 5.023998975753784
6.4 协程实现TCP服务端并发
1. 服务端
# 并发效果:一个服务端可以同时服务多个客户端
import socket
# 检测所有io
from gevent import monkey
from gevent import spawn
# 检测所有io阻塞
monkey.patch_all()
# 监听
def talk(sock):
# 通信循环
while True:
try:
data = sock.recv(1024)
# 不接受空信息
if len(data) == 0: break
# 展示客户端发送的信息
print(data.decode('utf-8'))
# 向服务端发送信息
sock.send(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()
2. 客户端
# 客户端开设几百个线程发消息
from threading import Thread, current_thread
from socket import *
def client_task():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
# 通信循环 where True: # 缩进下面所有的代码
# current_thread().name 获取线程的名 Thread-序号
msg = f'{current_thread().name}s ay hello'
# 发送信息
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_task)
t.start()
最厉害的情况:多进程下开设多线程, 多线程下开设协程.
以后可能自己动手写的不多, 一般都是使用别人封装好的模块或框架.
文章的段落全是代码块包裹的, 留言0是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言1是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言2是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言3是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言4是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言5是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言6是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言7是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言8是为了避免文章提示质量低.
文章的段落全是代码块包裹的, 留言9是为了避免文章提示质量低.