Python学习笔记Day11

1. 协程:协程是一种用户态的轻量级线程。

  • 协程的好处

    1. 无需线程上下文切换的开销
    2. 无需原子操作锁定及同步的开销
    3. 方便切换控制流,简化编程模型
    4. 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
  • 协程的缺点

    1. 无法利用多核:本质是单线程,它不能同时将单个CPU的多个核用上.
      协程需要和进程配合才能运行在多CPU上,一般都没有这个必要,除非是cpu密集型应用。
    2. 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

简单协程: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.事件驱动模型:

  1. 有一个事件(消息)队列;
  2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
  3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
  4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
  5. 事件处理完后会回调返回一个信号,继续中断前的任务。

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的封装

需要另外安装

原文地址:https://www.cnblogs.com/JeromeLong/p/13237076.html