进程池与线程池
计算机的性能是有限的,不能无限的开启进程和线程。
创建进程池,把并发任务的个数控制在计算机可承受范围之内
进程池(计算密集型用多进程)
方案1
方案1:等进程全部运行完毕统一拿返回值,需要处理返回值结果时采用并发的形式去处理结果效率最高,比较占用计算机的资源
from concurrent.futures import ProcessPoolExecutor
import os, time, random
def task(n):
print("%s is running" % os.getpid())
time.sleep(random.randint(1, 3))
return n ** 2
if __name__ == '__main__':
# print(os.cpu_count()) # 查看计算机的cpu是几核的,本机是8核的
pool = ProcessPoolExecutor(
max_workers=8) # 主进程开启进程池,ProcessPoolExecutor()如果不传参的话计算机的cpu是几核的,那么就开几个进程,传参的话,传多少值就开几个进程。
l = []
for i in range(19): # 进程池里面有8个进程,从始至终都是者8个进程,进程池里面的进程不会运行完一个就直接结束掉,会等这19个任务全部运行完毕才会结束进程
future = pool.submit(task, i) # .submit是异步的方式提交任务,task是需要运行的函数,i是传入task的参数
l.append(future)
pool.shutdown(wait=True) # shutdown(统计进程池里面的进程个数,不允许在往进程池里面提交新的任务了),wait=True,每运行完一个任务就会减一,直到任务减成0,等待所有任务运行完毕
print("主")
for obj in l:
print(obj.result()) # obj.result()等任务运行完毕统一拿返回值,这种方式采用并发的形式处理结果效率最高
方案2
方案2:利用回调函数让主进程去处理结果,每运行完一个进程,主进程就开启一个线程,这样对计算机资源的利用比较合理
from concurrent.futures import ProcessPoolExecutor
import os, time, random
def task(n):
print("%s is running" % os.getpid())
time.sleep(random.randint(1, 3))
return n ** 2
def handle(futrue):
res = futrue.result()
print("%s 正在处理结果:%s" % (os.getpid(), res))
time.sleep(2)
if __name__ == '__main__':
pool = ProcessPoolExecutor(max_workers=8)
for i in range(19):
pool.submit(task, i).add_done_callback(
handle) # .add_done_callback()回调函数,是指用来处理任务的函数,一旦一个任务运行完,会把运行完的结果当初参数传给被调的函数,会自动触发回调函数的运行
pool.shutdown(wait=True)
线程池代码演示(io密集型用多线程)
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import time, random
def task(n):
print("%s is running" % current_thread().name)
time.sleep(random.randint(1, 3))
return n ** 2
def handle(futrue):
res = futrue.result()
print("%s 正在处理结果:%s" % (current_thread().name, res))
time.sleep(2)
if __name__ == '__main__':
pool = ThreadPoolExecutor(max_workers=10) # ThreadPoolExecutor()如果不传参数的话,开启线程的个数默认时cpu个数的5倍
for i in range(19):
pool.submit(task, i).add_done_callback(
handle) # .submit异步提交,.add_done_callback()回调函数,是指用来处理任务的函数,一旦一个任务运行完,会把运行完的结果当初参数传给被调的函数,会自动触发回调函数的运行
pool.shutdown(wait=True)
协程就是单线程下实现的并发 (单线程最多可以抗住500多的并发)
特点:自己的应用程序实现多个人的调度
遇到io切换,可以将单线程的io降低到最低,提升单线程的运行效率
缺点:
1.不能实现并行
2.单线程下的多个任务一旦遇到io,整个线程都会阻塞,所有任务都会停滞
2、协程序的目的:
想要在单线程下实现并发
并发指的是多个任务看起来是同时运行的
并发=切换+保存状态
协程代码演示
下载安装gevent第三方库(cmd命令行里面输入命令pip3 install gevent)
协程代码演示Gevent(底层原理是封装了yield,遇到io保存当下的状态后会自动切换)
from gevent import monkey;monkey.patch_all() # monkey.patch_all()会把代码所有的io行为转换成gevent能够识别的
import gevent
import time
def eat(name):
print("%s eat 1" % name)
time.sleep(2) # 遇到io后切换到play
print("%s eat 2" % name)
def play(name):
print("%s play 1" % name)
time.sleep(1) # 遇到io后切换到eat,play也在io状态会进行来回的切换
print("%s play 2" % name)
g1 = gevent.spawn(eat, "nana")
g2 = gevent.spawn(play, name="nana")
g1.join() # print("主")运行完单线程就运行结束了,所有要用join让线程等待协程运行完再结束
g2.join()
print("主")
套接字程序实现协程并发案例
服务端
from gevent import monkey;monkey.path_all()
from socket import *
import gevent
def server(server_ip, port): # 链接请求
s = socket(AF_INET, SOCK_STREAM)
# s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind((server_ip, port)) # 绑定ip跟端口
s.listen(5) # 监听请求
while True:
conn, addr = s.accept() # 建立链接
gevent.spawn(talk, conn, addr) # 每建立一个链接提交一个协程对象,建立通信连接(异步提交)
def talk(conn, addr): # 通信请求
try:
while True:
res = conn.recv(1024)
print("client %s:%s msg:%s" % (addr[0], addr[1], res))
conn.send(res.upper())
except Exception as e:
print(e)
finally:
conn.close()
if __name__ == '__main__':
server("127.0.0.1", 8080)
客户端
__author__ = "上海吴彦祖"
from socket import *
client = socket(AF_INET, SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
while True:
msg = input("输入:").strip()
if not msg: continue
client.send(msg.encode("utf-8"))
msg = client.recv(1024)
print(msg.decode("utf-8"))