一、进程、线程池
#为什么用
开进程开线程都需要消耗资源消耗时间的,只不过两者比较的情况线程消耗的资源比较少
在成千上万个任务需要被执行的时候,我们可以去创建成千上万个进程么?
就算你比较二,建了无数个进程线程,系统敢让你执行么?除非它自己不要命了,辛辛苦苦创建出来还不能执行,气不气?
我们需要一个池子,根据计算机状况最大限度的限制进程线程数量,在计算机能够承受范围之内最大限度的利用计算机
#什么是池?
(硬件的发展跟不上软件的速度)
池其实是降低了程序的运行效率 但是保证了计算机硬件的安全
最大限度的限制进程线程数量,在保证计算机硬件安全的情况下最大限度的利用计算机
#提交任务的方式:
同步: 提交任务之后 原地等待任务的返回结果 期间不做任何事
异步: 提交任务之后 不等待任务的返回结果(异步的结果怎么拿???) 直接执行下一行代码
异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行
#创建的特点
池子中创建的进程/线程创建一次就不会再创建了
至始至终用的都是最初的那几个
这样的话节省了反复开辟进程/线程的资源
1.进程池
池中默认进程数: cpu个数
from concurrent.futures import ProcessPoolExecutor
import time
import os
pool = ProcessPoolExecutor(3) # 默认是当前计算机cpu的个数
def task(n):
print(n,os.getpid()) # 查看当前进程号,发现前三个和后三个进程号一样,进程池中三个进程重复利用
time.sleep(2)
return n**2,100
def call_back(n):
print('拿到了异步提交任务的返回结果:',n.result())
if __name__ == '__main__':
for i in range(6):
pool.submit(task,i).add_done_callback(call_back)
# 提交任务的时候,绑定一个回调函数call_back,
# 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值
# add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数
'''
0 9304
1 9060
2 12240
3 9304
拿到了异步提交任务的返回结果: (0, 100)
4 9060
拿到了异步提交任务的返回结果: (1, 100)
5 12240
拿到了异步提交任务的返回结果: (4, 100)
拿到了异步提交任务的返回结果: (9, 100)
拿到了异步提交任务的返回结果: (16, 100)
拿到了异步提交任务的返回结果: (25, 100)
'''
2、线程池
池中默认线程数: cpu*5
1.关闭池子后一起输出结果
pool.shutdown() #等待池子中所有的任务执行完毕之后 才会往下运行代码
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(3) # 不传默认是当前所在计算机的cpu个数乘5
def task(n):
print(n)
time.sleep(2)
return n**2
t_list = []
for i in range(6):
res = pool.submit(task,i)
# print(res.result()) # 原地等待任务的返回结果,这一句加上异步就变同步了,不能要
t_list.append(res)
pool.shutdown() # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码
for p in t_list:
print('>>>:',p.result())
'''
0
1
2
3
4
5
>>>: 0
>>>: 1
>>>: 4
>>>: 9
>>>: 16
>>>: 25
'''
2.异步回调输出结果
from concurrent.futures import ThreadPoolExecutor
from threading import currentThread
import time
pool = ThreadPoolExecutor(3) # 括号内可以传参数指定线程池内的线程个数,不传默认是当前所在计算机的cpu个数乘5
def task(n):
print(n,currentThread().name) #输出线程号会发现还是那3个在循环使用
time.sleep(2)
return n**2
def call_back(n):
print('拿到了异步提交任务的返回结果:',n.result())
for i in range(6):
pool.submit(task,i).add_done_callback(call_back)
# 提交任务的时候,绑定一个回调函数call_back,
# 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值
# add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数
'''
0 ThreadPoolExecutor-0_0
1 ThreadPoolExecutor-0_1
2 ThreadPoolExecutor-0_2
拿到了异步提交任务的返回结果: 0
3 ThreadPoolExecutor-0_0
拿到了异步提交任务的返回结果: 1
4 ThreadPoolExecutor-0_1
拿到了异步提交任务的返回结果: 4
5 ThreadPoolExecutor-0_2
拿到了异步提交任务的返回结果: 9
拿到了异步提交任务的返回结果: 16
拿到了异步提交任务的返回结果: 25
'''
二、协程
1.协程
进程:资源单位
线程:执行单位
协程:单线程下实现并发(完全是程序员自己意淫出来的名词)
优点:协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 单线程内就可以实现并发的效果,最大限度地利用cpu
2.协程的实现:
进程三态中我们知道只要进程遇到阻塞就会被系统强制剥夺cpu权限,怎样不让剥夺呢?
当然是我们自己主动去发现io并尽量改正,好好表现争取更多cpu资源
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中,即用户程序级别,主动监控IO,尽量不让操作系统操心
当单线程下的多个任务在一个任务遇到io阻塞时就立刻切换到另外一个任务去计算
这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来
从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程,大大提升效率
4.提高效率的终极方法:
多进程下开多线程
多线程下再开协程

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)
#我们能不能通过yield来切换并保存状态呢?
运行发现不能提高效率也不能识别IO更别说自动切换了,浪费感情

# 效率还没串行高,并且还不能识别IO更别说自动切换了
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)
#gevent模块
from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行 小猴可以监控任何IOfrom gevent import spawn #这个可以识别少量IO主要用来切换加保存,监控交给小猴来做
from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行
from gevent import spawn
import time
"""
注意gevent模块没办法自动识别time.sleep等io情况
需要你手动再配置一个参数,即小猴模块,小猴可以发现所有IO
"""
def heng():
print("哼")
time.sleep(2)
print('哼哼')
def ha():
print('哈')
time.sleep(3)
print('哈哈')
def heiheihei():
print('嘿')
time.sleep(5)
print('嘿嘿')
start = time.time()
g1 = spawn(heng)#spawn类似一个列表,把所有函数都放进去,程序一运行,他监测所有任务,一旦发现有IO立马换任务执行
g2 = spawn(ha) # spawn对任务的切换是在代码层面上,操作系统不会发现,只会以为该进程一直在干活,就不会剥夺CPU权限
g3 = spawn(heiheihei) #spawn只要传进函数名字会自动加括号调用
# g1.join()
# g2.join()
g3.join() #如果你不等,这里可就这一个主线程,直接就往下执行输出代码,代码完程序立刻停止,一个函数也不会执行
print(time.time() - start) #5s多一点
#基于gevent模块的TCP服务端单线程实现并发
#服务端
from gevent import monkey;monkey.patch_all()
import socket
from gevent import spawn
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
def talk(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close()
def server1():
while True:
conn, addr = server.accept()
spawn(talk,conn) #监控talk中的IO,并传入参数conn
if __name__ == '__main__':
g1 = spawn(server1) #监控accept的IO
g1.join()
#客户端
import socket
from threading import Thread,current_thread
def client():
client = socket.socket()
client.connect(('127.0.0.1',8080))
n = 0
while True:
data = '%s %s'%(current_thread().name,n)
client.send(data.encode('utf-8'))
res = client.recv(1024)
print(res.decode('utf-8'))
n += 1
for i in range(400):
t = Thread(target=client)
t.start()
三、IO模型
本文讨论的背景是Linux环境下的network IO。本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”,Stevens在这节中详细说明了各种IO的特点和区别,如果英文够好的话,推荐直接阅读。Stevens的文风是有名的深入浅出,所以不用担心看不懂。本文中的流程图也是截取自参考文献。
Stevens在文章中一共比较了五种IO Model:
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* asynchronous IO 异步IO
* signal driven IO 信号驱动IO
由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。
