python协程
进程是资源分配的最小单位,线程是CPU调度的最小单位
进程
开销大 数据隔离(资源分配的最小单位) 能够利用多核
线程
开销小 资源共享(cpu调度的最小单位) 能够利用多核(Cpython解释器下不行)
Cpython解释器
高计算型 开多进程
高IO型 开多线程
所以大部分情况下,基本上只要有网络操作,开启多线程一定可以提高这一整个程序的cpu使用率
线程
GIL 全局解释器锁 导致了线程不能利用多核
线程之间数据安全的问题
Lock互斥锁
Rlock递归锁
死锁现象: 同一个线程中,出现了两把锁,同时锁两个资源去进行某些操作
线程队列
先进先出的队列
后进先出的栈
优先级队列
池
concurrent.futrues
处理文件操作(处理日志文件) 处理网络并发(请求爬虫的页面)
ThreadPoolExcutor ProcessPoolExcutor
submit
shutdown
map
result
add_done_callback
协程介绍
线程中能够放多个任务在一个线程中,其中的每一个任务都可以成为一个协程,每一个协程都可以在一条线程中任意的切换
线程: cpython解释器 只能用一核 只有有IO操作的多个任务才适合起多线程
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率
优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
串行执行
import time
def consumer(res):
'''任务1:接收数据,处理数据'''
pass
def producer():
'''任务2:生产数据'''
res=[]
for i in range(10000000):
res.append(i)
return res
start=time.time()
#串行执行
res=producer()
#写成consumer(producer())会降低执行效率
consumer(res)
stop=time.time()
print(stop-start)
基于yield并发执行
import time
def consumer():
'''任务1:接收数据,处理数据'''
while True:
x=yield
def producer():
'''任务2:生产数据'''
g=consumer()
next(g)
for i in range(10000000):
g.send(i)
start=time.time()
#基于yield保存状态,实现两个任务直接来回切换,即并发的效果
producer()
stop=time.time()
print(stop-start)
greenlet模块介绍:
模块安装: pip install greenlet
import time
from greenlet import greenlet
def eat():
print('start eating')
# 保存当前函数的状态 切换到对应的函数中去执行
g2.switch()
time.sleep(1)
print('eating finished')
def sleep():
print('start sleeping')
time.sleep(1)
print('sleeping finished')
g1.switch()
# 创建了一个协程对象
g1 = greenlet(eat)
# 创建了一个协程对象
g2 = greenlet(sleep)
g1.switch()
yield 快
greenlet 慢
gevent模块介绍:
pip3 install gevent
使用协程 是为了规避io,因为程序:遇到io就切换
gevent遇到IO就切换,基于greenlet进行切换
from threading import currentThread
from gevent import monkey
monkey.patch_all()
import time
# 内部使用了greenlet的切换机制 实现了遇到IO自动进行切换
import gevent
def eat():
# DummyThread-1 伪线程
print('start eating',currentThread())
time.sleep(1)
print('eating finished')
def sleep():
# DummyThread-1 伪线程
print('start sleeping',currentThread())
time.sleep(1)
print('sleeping finished')
g_l = []
for i in range(5):
#创建一个协程对象g1,spawn括号内第一个参数是函数名,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数
g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
g_l.append(g1)
g_l.append(g2)
#等待g1结束
# g1.join()
# g2.join()
gevent.joinall(g_l)
两个协程模块
gevent - 基于greenlet - 使用更方便 性能相对低
asyncio - 基于yield - 性能更好
flask 支持协程 gevent的
twisted sanic tornado 异步框架 用的是asyncio协程模块
让gevent识别的io操作是来自其他模块的(要写在其他模块之前)
from gevent import monkey
monkey.patch_all()
import gevent
import time
from urllib import request
def get_page(url_t):
ret = request.urlopen(url_t[0])
content = ret.read()
with open(url_t[1]+'_new','wb') as f:
f.write(content)
url_lst = [
('http://www.sogou.com','sogou'),
('http://www.baidu.com','baidu'),
('http://www.douban.com','douban'),
('http://www.cnblogs.com','cnblogs'),
('https://www.redhat.com/en','redhat'),
('https://www.centos.org/','centos'),
('https://www.liaoxuefeng.com/','lxf'),
('http://www.jd.com','jd'),
('http://www.taobao.com','tb')
]
start = time.time()
g_l = []
for url_t in url_lst:
g = gevent.spawn(get_page,url_t)
g_l.append(g)
gevent.joinall(g_l)
print(time.time() - start)
协程模拟socket server多客户端通信
server端:
from gevent import monkey
monkey.patch_all()
import socket
import gevent
def talk(conn):
while True:
msg = conn.recv(1024).decode()
ret_msg = msg.upper().encode()
conn.send(ret_msg)
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
conn,_ = sk.accept()
g = gevent.spawn(talk,conn)
client端:
import socket
import threading
def client():
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
sk.send(b'hello')
msg = sk.recv(1024)
print(msg)
for i in range(500):
threading.Thread(target=client).start()
总结:
asyncio的状态切换是由yield关键字完成的
greenlet 扩展模块 进行函数中代码的来回切换
在其他编译型语言中 由于多线程可以利用多核 所以协程这个概念被弱化了
对于Cpython 多线程也不能利用多核 所以协程这个概念就变得至关重要了
协程的本质是一条线程
1.不能利用多核
2.用户级的概念,操作系统不可见
3.协程不存在数据安全问题
切换 一条线程
操作系统感知到线程一直在运行 就减少了线程进入阻塞状态的次数,从而提高了效率
协程实际的定义: 在一条线程之间来回切换
1.yield和函数作比较 yield进行切换实际上会浪费时间,即便是程序级别的切换也会浪费时间
2.500个client并发的server的效率计算,协程的处理并发能力 很强
3.geturl爬虫的练习,协程和函数做对比,协程的速度快 gevent去处理问题实际上很简便 直接扔给spawn就行了
建议
进程数 cpu个数的1-2倍 对于其他的语言 来说 进程是不常开的
线程数 20
协程数 500并发
进程 + 协程
开5个进程,在进程中开协程 既能够利用多核 也可以提高处理IO的效率
线程
处理 文件 ,input
进程 8个进程
协程 1000个
4*500 = 8000并发

浙公网安备 33010602011771号