协程

一、协程简介

协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
协程优势:1.无需线程上下文切换的消耗 2.无需加锁,协程就是一种单线程模式,串行不用锁 3.高并发,高扩展性 4.协程能保留上次调用的状态,重新进入时候继续上次退出地方。


我的理解

协程简单点说,就是一个进程可以启动多个线程,每个线程中都可以开启很多个协程,完成不同事件的处理,这就是协程高并发的特点。因为协程用到的不多,我们就不讲解底层的代码了,协程封装在greenlet模块中,我们在使用时候调用这个模块即可。
举一个例子更好的理解协程。我们在一个代码中启动两个协程,分别完成两个任务,假设第一个任务需要下载文件(需要花5s时间),我们都知道python实际上都是单线程模式,不管干嘛都要一个一个执行,但是可以通过切换的方式来使得程序显得是在多路同时处理。那么我们开启的两个协程,正常情况是,先执行协程1,花费5s。在执行协程2,假设花费3s。一共就花费了8s。我们发现这是一种效率极低的方式。如果有上万个协程执行,可想而知需要花费多久的时间。
那么我们可不可以在执行协程1时候,遇到下载(也就是I/O操作)就转而执行协程2,等协程2执行完,再回来执行协程1。这样就会花费最长的一个协程执行的时间,也就是5s。下面举一个简单的代码来验证一下:

from greenlet import greenlet  #导入封装好的协程

def test1():
    print(12)
    gr2.switch()  #我们这里模拟I/O操作(上面说的下载5s),遇到这种就跳转到gr2协程执行
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)

gr1=greenlet(test1)
gr2=greenlet(test2)  #启动两个协程
gr1.switch()  #手动切换协程,执行gr1

这里写图片描述
根据上面的代码我相信大家都会看出执行结果,这就是协程之间的切换,一旦特别多的协程启动,就会大大缩短时间,那么如果有很多协程启动,我们在进行手动切换就会容易出错,所以看一下自动切换协程的方法,自动切换协程封装在gevent中:


import gevent  #自动切换协程

def foo():
    print('Running in foo')
    gevent.sleep(2)  #模拟IO操作,耗费2s时间,跳转到下一个协程
    print('Running in foo again')

def bar():
    print('Explicit context switch to bar')
    gevent.sleep(1)   #遇到I/O操作,跳转到下一个协程
    print('Explicit context switch to bar again')

def zoo():
    print('Explicit context switch to zoo')
    gevent.sleep(0)  #0秒也是进行了I/O操作
    print('Explicit context switch to zoo again')

gevent.joinall ([
    gevent.spawn(foo),   #开启3个协程,每个协程调用不同的函数
    gevent.spawn(bar),
    gevent.spawn(zoo),
])

看一下结果,我们分析一下:
这里写图片描述
先开启协程foo,打印Running in foo,遇到I/O操作2s跳转到协程bar,打印Explicit context switch to bar,遇到I/O操作1s,跳转到协程zoo,打印Explicit context switch to zoo,遇到I/O操作0s跳转到协程foo,发现2s没到,切换到协程bar,发现1s没到,切换到协程zoo,0s到了,打印Explicit context switch to zoo again,切换协程foo,2s没到,切换协程bar,1s到了打印Explicit context switch to bar again,最后切换回foo,打印Running in foo again。这样就完成了自动切换,本来执行需要3s,现在只需要2s。


二、协程应用

1.协程之爬虫

我们已经了解了协程在进行高并发时候如何工作,下面我们说一个很经典的协程可以使用的场景,爬虫我不知道大家了不了解,过一阶段我会写一些爬虫的文章,包括爬虫底层代码和scrapy框架等。在进行爬虫时候,我们要获取很多的数据,如果每一次只等着上一次的内容爬取完在进行下次爬取效率很低,我们可以使用协程的方式:
假设启动一个进程,创建100个线程,每个线程中启动1000个协程,每个协程爬取10个网站,那么我们同时就可以爬取100万个网站信息了。那么如何利用协程进行数据的爬取呢?我这里写一个最基础的爬虫代码,如果不理解就简单查一下语句用法即可:

from urllib import  request  #爬取网页的模块
import gevent  
#gevent默认情况检测不到urllib 所以不会进行协程切换,爬取三个网页时间和串行爬取三个网页时间一样大概4秒

#那么如何让gevent知道urllib进行了IO操作呢:打一个补丁monkey就可以了
from gevent import monkey
monkey.patch_all()  #把当前程序的所有IO操作单独做上标记,这样gevent就可以进行切换了,时间减小为2秒左右。

def f(url):  #传入参数:网址
    print('GET:%s'%url)
    resp=request.urlopen(url)
    data=resp.read()  #获取网页信息
    print('%d bytes received from %s' % (len(data), url)) #打印网页数据大小和网址

gevent.joinall ([
    gevent.spawn(f,'https://www.python.org/'),  #带参数(url)方式开启协程
    gevent.spawn(f,'https://www.yahoo.com/'),
    gevent.spawn(f,'https://github.com/'),
])

看一下结果:
这里写图片描述
访问3个网页数据的时间由4s缩短到2s,可想而知如果获取上万个网站信息,效率会大大提高。


2.协程之ftp

ftp我就不多说了,不懂的可以看看我另一篇文章:
https://mp.csdn.net/mdeditor/81502058
那么协程怎么实现socket通信呢?我们先写一个server端:

import socket,time,gevent,sys
from gevent import monkey
monkey.patch_all()  #补丁

def server(port):
    s=socket.socket()
    s.bind(('0.0.0.0',port))  #端口号
    s.listen(500)
    while True:
        cli,addr=s.accept()  #接受到的数据返回给cli,addr

        #过来一个客户端链接(就是说有一个客户端连接),就交给一个协程去处理,调用函数handle_request
        gevent.spawn(handle_request,cli) 

def handle_request(conn):
    try:
        while True:
            data=conn.recv(1024)  #接收客户端发来的数据
            print('recv:',data)
            conn.send(data)     #把接收来的数据发回给客户端
            if not data:
                conn.shutdown(socket.SHUT_WR)
    except Exception as ex:
        print(ex)
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)

写一下client:

#我们打开5个客户端, 通过协程的方式就实现了多线程的方式,可以让多个客户端连接服务器
import socket
HOST='localhost'
PORT=8001
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((HOST,PORT))  #连接服务器
while True:
    msg=bytes(input('>>:'),encoding= 'utf-8') #用户输入发送的数据
    s.sendall(msg)  #发数据给客户端
    data=s.recv(1024) #收数据
    print('Received',repr(data))  #repr格式化输出
s.close()

看一下结果:
这里写图片描述
我们发现服务器可以接收到5个客户端发来的数据,111和11111111都是客户端1发来的以此类推,我们看一下服务器是否给客户端返回了数据,看一下客户端3
这里写图片描述
这样就实现了协程的socket通信,如果有成千上万个客户端想要连接服务器时候,使用这种架构也是不错的。
协程就说到这里了,进程、线程、协程这三个还是跟重要的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值