路飞学城Python-Day32

36-进程池线程池
开多线程实现并发的效率是高的,当用户没有那么多的时候,服务器是可以承受压力的
但是一定要以某种方式来设置并发数,让服务器能够实现稳定的运行,控制服务器的线程数
设置池,往里面放池的数量限制,进程池就是往进程池里放进程数,线程池就是往池里放线程数
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from multiprocessing import Process
import os
import time
import random
# 线程池和进程池的接口是一样的,那么什么时候用进程池,什么时候用线程池
# 池本质就是开进程和线程,本质没有区别
# 计算密集型,需要用多核的优势时候就要用进程池
# I/O密集型,不需要过多CPU资源的时候就要用线程池
 
 
def task(name):
    print('name is %s pid is %s'%(name, os.getpid()))
    time.sleep(random.randint(1,3))
 
 
if __name__ == '__main__':
    # 进程池最多装4个进程
    # pool = ThreadPoolExecutor(4)
    pool = ProcessPoolExecutor(4)
    for i in range(10):
        # 异步调用,提交完任务以后,不用等任务拿到结果,只负责任务做完
        pool.submit(task,'panda %s'%i)
    pool.shutdown()
    print('主线程')
 

37-异步调用与回调机制
提交任务的两种方式
1.同步调用
# 1.同步调用:提交完任务后,就等待任务执行完毕,拿到结果,再执行下一行代码
# 同步调用的结果就是串行执行的方式
import time
import random
from concurrent.futures import ThreadPoolExecutor
 
 
def la(name):
    print('%s is laing' % name)
    time.sleep(random.randint(1,3))
    res = random.randint(7,13)*"*"
    return {'name':name, 'res':res}
 
 
def weight(obj):
    name = obj['name']
    size = obj['res']
    print('%s is %s'%(name, size))
 
 
if __name__ == '__main__':
    pool = ThreadPoolExecutor(10)
    res1 = pool.submit(la, 'panda').result()
    weight(res1)
    res2 = pool.submit(la, 'zombie').result()
    weight(res2)
    res3 = pool.submit(la, 'boy').result()
    weight(res3)
2.异步调用
# 2.异步调用:提交完任务以后不在原地等待任务执行完毕,
import time
import random
from concurrent.futures import ThreadPoolExecutor
 
 
def la(name):
    print('%s is laing' % name)
    time.sleep(random.randint(1,3))
    res = random.randint(7,13)*"*"
    return {'name':name, 'res':res}
 
 
def weight(obj):
    obj = obj.result()
    name = obj['name']
    size = obj['res']
    print('%s is %s'%(name, size))
 
 
if __name__ == '__main__':
    pool = ThreadPoolExecutor(10)
    pool.submit(la, 'panda').add_done_callback(weight)
    pool.submit(la, 'zombie').add_done_callback(weight)
    pool.submit(la, 'boy').add_done_callback(weight)
 
 

38-进程池线程池小练习
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
import requests
import time
 
def get(url):
    print('GET %s' % url)
    response = requests.get(url)
    time.sleep(3)
    return {'url':url, 'content':response.text}
 
 
def parse(res):
    res = res.result()
    print('%s parse res is %s' % (res['url'], len(res['content'])))
 
if __name__ == '__main__':
    urls = {
        ' https://www.processon.com/',
    }
    pool = ThreadPoolExecutor(2)
    for i in urls:
        pool.submit(get,i).add_done_callback(parse)

39-协程介绍
单线程下实现并发的效果,指定单线程下的对CPU不断的进行切换计算,就可以实现并发的效果
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率

40-协程实现与总结
协程就是单线程下实现并发,自己在代码级别实现控制
总结协程特点:
  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

41-greenlet模块
greenlet封装程度高,可以实现多个程序之间来回切换,无法实现检测I/O后才切换程序
from greenlet import greenlet
 
 
def eat(name):
    print('%s eat 1' % name)
    g2.switch('panda')
    print('%s eat 2' % name)
    g2.switch()
 
def play(name):
    print('%s play 1' % name)
    g1.switch()
    print('%s play 2' % name)
 
g1 = greenlet(eat)
g2 = greenlet(play)
# 切换
g1.switch('panda')
 

42-gevent模块
本质上就是封装了gevent模块,能够实现检测I/O,检测到I/O就会实现切换的操作
import gevent
import time
from gevent import monkey
# 打补丁,所有的设计IO操作的部分都全部打上补丁,做上标记
monkey.patch_all()
 
def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)
 
 
def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)
 
g1 = gevent.spawn(eat, 'panda')
g2 = gevent.spawn(play, 'boy')
g1.join()
g2.join()
 

43-gevent异步提交任务
使用gevent模块就需要打补丁,一定要记得from gevent import monkey;monkey.patch_all()
import gevent
import time
from gevent import monkey;monkey.patch_all()
# 打补丁,所有的设计IO操作的部分都全部打上补丁,做上标记
 
 
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
 
 
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
 
g1 = gevent.spawn(eat, 'panda')
g2 = gevent.spawn(play, 'boy')
gevent.joinall([g1,g2])

44-基于gevent模块实现并发的套接字通信
gevent模块是单线程下实现多个IO密集型操作的模块操作方式
服务端
import socket
from gevent import monkey,spawn;monkey.patch_all()
def comm(conn):
while True:
try:
data = conn.recv(1024)
if not data:break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
 
 
def server(ip,port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((ip, port))
server.listen(5)
while True:
conn, addr = server.accept()
spawn(comm, conn)
server.close()
 
if __name__ == '__main__':
g = spawn(server('127.0.0.1', 8080))
g.join()
客户端
import socket
from threading import Thread,currentThread
def client():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
client.send(('%s hello'%currentThread().getName()).encode('gbk'))
data = client.recv(1024)
print(data.decode('gbk'))
client.close()
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
 

45-IO模型介绍
为什么要介绍IO模型?
协程是单线程下的并发,一个线程就能支持500个以上的并发量
同步调用
提交任务的方式:提交完任务以后,在等待结果,拿到结果以后执行下面的代码
异步调用
提交任务的方式:提交完任务以后,不等待结果,接着就是执行下面的代码,异步通常是要和回调函数一起执行的,
因为异步是不等待函数的结果的,但是始终需要结果,自动触发回调函数
注意:同步不等于阻塞
阻塞:遇到IO以后,如果自己不做处理,操作系统就会重新调用CPU,操作系统解决的就是IO阻塞问题
非阻塞:指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程
blocking IO 阻塞IO
nonblocking IO 非阻塞IO
IO multiplexing IO多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步IO
遇到IO就会阻塞,遇到网络IO回原地阻塞
1.服务端什么样的操作属于IO?
服务端的套接字,accept()就属于阻塞,等待链接;recv()、send()也是IO行为
2.为什么这个IO行为会让用户感觉在等待?
recv经历了两个阶段:1.等待数据的阶段;2.把数据冲操作系统的缓存拷贝数据到应用程序阶段
send经历一个阶段:1.从应用程序拷贝数据到操作系统(数据量大的也会产生阻塞)

46-阻塞IO模型
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来
阻塞IO导致程序不能实现并发的效果
线程池可以保证机器在健康的状态下稳定的运行

47-非阻塞IO模型
数据一旦已经到达本地的缓冲区以后,一定要把本地的缓冲区的数据拷贝给操作系统
非租塞IO的作用就是在等待数据的阶段让应用程序不再等待,而是继续执行后面的代码,一段时间后过来再接收数据
这样的操作叫做轮询
1.非阻塞IO在做其他事的时候会有数据来,但是不能及时响应,导致数据不能及时响应
2.服务端没有任务阻塞,就是一个死循环,对于机器来说压力太大了,这一个线程一直处于就绪状态,CPU给服务端的资源太多了,是无用的轮询方式,导致CPU无用的占用太多

48-多路复用IO模型
多路复用IO模型->IO多路复用->IO事件驱动模型
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,
当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用\(select和recvfrom\),
而blocking IO只调用了一个系统调用\(recvfrom\)。但是,用select的优势在于它可以同时处理多个connection。
阻塞阶段经历了2个阶段就拿到数据,IO多路复用经历了3个阶段,多了中间的select阶段
select的性能高是因为可以同时监控多个IO阻塞,如果select检测的IO就一个的话,性能是不如单个的纯IO阻塞的
要基于select实现套接字的并发,肯定是用来实现多个IO阻塞的操作
 
select监听fd变化的过程分析:
用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后,
就会发送信号给用户进程数据已到;
用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除,
这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。
 
该模型的优点:
相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。
如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。
 
该模型的缺点:
首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。
很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。
如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,
所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。
其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。

49-异步IO模型
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

猜你喜欢

转载自www.cnblogs.com/pandaboy1123/p/9393918.html
今日推荐