协程

一、协程存在的原因

  因为想要在单线程内实现并发的效果(因为CPython有GIL锁,限制了在同一个时间点,只能执行一个线程,所以想要在执行一个线程的期间,充分的利用CPU的性能,因此想在单线程内实现并发的效果) 

  并发 :切换 + 保存状态 (yield 只能单纯的实现状态的保存和切换函数)

             yield不能实现:当某一个函数中遇到IO阻塞时,自动的切换到另一个函数去执行

             目标是:当某一个函数中遇到IO阻塞时,程序能自动的切换到另一个函数去执行

              如果能实现这个功能,那么每个函数都是一个协程。

  CPU是为什么要切换?

    1 、因为某个程序阻塞了

    2、因为某个程序时间片用完了

  想要实现单线程的并发,就要解决在单线程内,多个任务函数中,某个任务函数遇见IO操作,马上自动切换到其他任务函数去执行。

 

二、协程

  1、含义:是一个比线程更加轻量级的单位,是组成线程的各个函数,协程本身没有实体

  2、greenlet模块:能简单的实现函数与函数之间的切换,但是遇到IO操作,不能自动切换到其他函数中。

    (1)注册一下函数func,将函数注册成一个对象f1

      f1 = greenlet(func)

    (2)调用func,使用f1.switch(),如果func需要传参,就在switch这里传即可

  3、gevent模块:可以实现在某函数内部遇到IO操作,就自动的切换到其他函数内部去执行

         gevent不识别别的IO操作,例如:time.sleep(1)就不识别,不会进行函数的切换

         我们可以通过mokey来解决gevent不识别其他IO操作的现象

         mokey.patch_all()  可以gevent识别大部分常用的IO操作

    g = gevent.spawn(func,参数) 注册一下函数func,返回一个对象g

      gevent.join(g) 等待g指向的函数func执行完毕,如果在执行过程中,遇到IO,就切换

      gevent.joinall([g1,g2,g3]) 等待g1,g2,g3指向的函数func执行完毕

import gevent
import time

def func():
    print(111)
    time.sleep(1) # gevent不识别除了自己以外的IO操作,
    gevent.sleep(1) # 执行被阻塞,切换函数去执行别的函数
    print(222)

def func1():
    time.sleep(1)
    print(333)
    gevent.sleep(1)
    print(444)

g1 = gevent.spawn(func) # 实例化一个对象
g2 = gevent.spawn(func1)
g2.join()
g1.join() # 把等待g1的函数执行完毕
串行和并发的效率对比
from gevent import monkey
monkey.patch_all() # gevent可以识别time.sleep()IO操作,是一个阻塞。
import time
import gevent

def func(i):
    time.sleep(1)
    print(i)

start = time.time()
for i in range(10):
    func(i)
print(time.time() - start)

start1 = time.time()
l = []
for i in range(10):
    g = gevent.spawn(func,i)
    l.append(g)
gevent.joinall(l)
print(time.time() - start1)

 

三、进程、线程、协程区别

  计算密集用多进程,可以充分的利用多核CPU的性能

  IO密集用多线程,(注意:协程是单线程的)

  多线程和协程的区别是 :

    线程是由操作系统调度,控制

    协程是由程序员自己调度,控制

 

四、IO多路复用

  阻塞IO

  非阻塞IO

  多路复用IO

  异步IO  python实现不了,但是有tornado框架,天生自带异步

 

五、select   和  poll   和epoll  的区别

  select 和poll有一个共同的机制,都是采用轮询的方式去询问内核,有没有数据准备好了。

  select有一个最大监听事件的限制,32位机限制1024,64位机限制2048

  poll没有,理论上poll可以开启无限大,1G内存大概可以开10W个事件去监听

  epoll是最好的,采用的是回调机制,解决了select和poll共同存在的问题

  而且epoll理论上也可以开启无线多个监听事件

# 基于select的网络IO模型
import select
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
del_l = []
rlist = [sk] # 是用来让select帮忙监听的 所有接口
# select:Windows/Linux 是监听事件有没有数据到来
# poll:Linux  也可以做select的工作
# epoll:Linux  也可以做类似的工作

while 1:
    r,w,x = select.select(rlist,[],[]) # 传参给select,当rlist列表中哪个接口有反应,就返回给r这个列表
    if r:
        for i in r: # 循环遍历r,看看有反应的接口到底是sk,还是conn
            if i == sk:
                '''sk有数据要接收,代表着有客户端要来连接'''
                conn,addr = i.accept()
                rlist.append(conn) # 把新的客户端的连接,添加到rlist,继续让select帮忙监听
            else:
                try:
                    msg = i.recv(1024).decode('utf-8')
                    if not msg:
                        '''客户端关闭了连接'''
                        del_l.append(i)
                        i.close()
                    else:
                        i.send(msg.upper().encode('utf-8'))
                except ConnectionResetError:
                    pass
        if del_l: # 删除那些主动断开连接的客户端的conn
            for i in del_l:
                rlist.remove(i)
            del_l = []

 

  

posted on 2018-08-28 22:36  窮山霧繞(静妙)  阅读(129)  评论(0编辑  收藏  举报

导航