目录
1. 协程:协程是一种用户态的轻量级线程。
-
协程的好处
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
-
协程的缺点
- 无法利用多核:本质是单线程,它不能同时将单个CPU的多个核用上.
协程需要和进程配合才能运行在多CPU上,一般都没有这个必要,除非是cpu密集型应用。 - 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
- 无法利用多核:本质是单线程,它不能同时将单个CPU的多个核用上.
简单协程:yield
,__next__()
greenlet 协程,手动切换
# 手动协程切换
from greenlet import greenlet
def test1():
print(12)
gr2.switch() # 切换到协程gr2
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch() # 切换到协程gr1
print(78)
gr1 = greenlet(test1) # 启动一个协程
gr2 = greenlet(test2)
gr1.switch() # 切换到协程gr1
gevent 自动切换协程,是greenlet的封装
# 自动协程切换
import gevent
def foo():
print('Running in foo')
gevent.sleep(2) # 模仿io切换,切换出去,2s后io完成并切回来才执行
print('Explicit context switch to foo again')
def bar():
print('Explicit精确的 context to bar')
gevent.sleep(1)
print('Implicit context switch back to bar')
def func3():
print("running func3 ")
gevent.sleep(0)
print("running func3 again ")
gevent.joinall([ # 相当于pool进程池
gevent.spawn(foo), # 生成gevent协程
gevent.spawn(bar),
gevent.spawn(func3),
])
2. 简单爬虫
from urllib import request
import gevent,time
from gevent import monkey
monkey.patch_all() # 把当前程序的所有的io操作给我单独的做上标记 urllib要进行io操作
def f(url):
print('GET: %s' % url)
resp = request.urlopen(url) # 请求网页
data = resp.read() # 读取结果
print('%d bytes received from %s.' % (len(data), url))
# 同步,串行
urls = ['https://www.python.org/',
'https://www.ithome.com/',
'https://github.com/' ]
time_start = time.time()
for url in urls:
f(url) # 批量爬虫
print("同步cost",time.time() - time_start)
async_time_start = time.time()
# 异步,并行
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.baidu.com/'),
gevent.spawn(f, 'https://github.com/'),
])
print("异步cost",time.time() - async_time_start)
3.事件驱动模型:
- 有一个事件(消息)队列;
- 鼠标按下时,往这个队列中增加一个点击事件(消息);
- 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
- 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
- 事件处理完后会回调返回一个信号,继续中断前的任务。
4.io模式:
- 阻塞io:遇到io阻塞,等待返回信号,然后将数据从内核拷贝到用户空间
- 非阻塞io:不断发送io请求
- io多路复用:多路socket同时请求,有信号返回,三种:selec、poll、epoll,必须非阻塞才能多路复用
- 信号驱动io
- 异步io:请求后不等待,io准备好直接拷贝数据到用户空间,然后才通知用户
5.select IO多路复用
select模拟socketserver
6.selector 模块:默认用epoll,如果系统没有epoll就用select
import selectors
import socket
sel = selectors.DefaultSelector() # 实例selector对象
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready
print('accepted', conn, 'from', addr,mask)
conn.setblocking(False) # 设置非阻塞
sel.register(conn, selectors.EVENT_READ, read) # 新连接再活动时注册read回调函数,处理活动数据
def read(conn, mask):
data = conn.recv(1024) # Should be ready
if data:
print('echoing', repr(data), 'to', conn)
conn.send(data) # Hope it won't block
else:
print('closing', conn)
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.bind(('localhost', 9999))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept) # 新连接注册accept回调函数,创建连接
while True:
events = sel.select() # 默认io阻塞,有活动连接就返回活动的连接列表
for key, mask in events:
callback = key.data # 就是accept或者read
callback(key.fileobj, mask) # key.fileobj = fd文件句柄(r)
7. RabbitMQ 消息队列,可实现任意进程间的消息传递,实质上是一个socket的封装
py 进程queue 可允许父进程与子进程间queue访问,或子进程之间,进程间数据传递
未学完,需要另外安装
https://www.cnblogs.com/alex3714/articles/5248247.html
8. Redis 实现数据的共享,数据在内存,实质上也是一个socket的封装
需要另外安装