洗礼灵魂,并发爬网页

By admin in 4858.com on 2019年3月31日

协程

协程介绍及大旨示例

协程,又称微线程,纤程。英文名Coroutine。一句话表明哪些是协程:协程是一种用户态的轻量级线程

  协程拥有自个儿的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到任哪个地点方,在切回到的时候,苏醒原先保留的寄存器上下文和栈。因而:

协程能保留上一回调用时的意况(即具备片段景况的1个特定组合),每一回经过重入时,就一定于进入上三遍调用的动静,换种说法:进入上三遍离开时所处逻辑流的地点。

  协程的益处:

  • 无需线程上下文切换的开销
  • 无须原子操作锁定及共同的支付
    • “原子操作(atomic
      operation)是不要求synchronized”,所谓原子操作是指不会被线程调度机制打断的操作;那种操作一旦初叶,就平素运营到结束,中间不会有其它context switch
      (切换来另贰个线程)。原子操作可以是三个手续,也足以是八个操作步骤,可是其顺序是不可能被打乱,恐怕切割掉只实行部分。视作全部是原子性的中坚。
  • 方便人民群众切换控制流,简化编程模型
  • 高并发+高扩充性+低本钱:八个CPU支持上万的协程都符合规律。所以很吻合用来高并发处理。

  缺点:

  • 心慌意乱运用多核实资金源:协程的齐云山真面目是个单线程,它不能同时将 单个CPU
    的七个核用上,协程需求和进度合作才能运作在多CPU上.当然大家司空眼惯所编纂的多方面采取都不曾这几个必要,除非是cpu密集型应用。
  • 进展围堵(Blocking)操作(如IO时)会卡住掉全部程序。

三、协程 

3.1协程概念

协程:又称微线程,纤程。英文名Coroutine。一句话表达什么是线程:协程是一种用户态的轻量级线程。

  协程拥有和谐的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其余地方,在切回到的时候,苏醒原先封存的寄存器上下文和栈。由此:协程能保存上一回调用时的情况(即全体片段景况的三个特定组合),每一回经过重入时,就一定于进入上贰回调用的状态,换种说法:进入上一遍离开时所处逻辑流的任务。

  协程的适用场景:当程序中存在大气不须要CPU的操作时(IO),适用于协程

 

协程的益处:

  • 无需线程上下文切换的付出

  • 不要原子操作锁定及一块的开支方便切换控制流,简化编制程序模型

  ”原子操作(atomic
operation)是不须要synchronized”,所谓原子操作是指不会被线程调度机制打断的操作;那种操作一旦起头,就一向运营到截至,中间不会有别的context switch
(切换来另二个线程)。原子操作可以是一个步骤,也得以是多少个操作步骤,但是其顺序是不得以被打乱,恐怕切割掉只实行部分。视作全体是原子性的核心。

  • 高并发+高扩展性+低本钱:一个CPU协助上万的协程都不是题材。所以很合乎用来高并发处理。

协程的症结:

  • 没辙利用多核实资金源:协程的实质是个单线程,它无法而且将 单个CPU
    的多少个核用上,协程需求和进度协作才能运维在多CPU上.当然大家普通所编写的大举利用都尚未那几个须求,除非是cpu密集型应用。

  • 开始展览围堵(Blocking)操作(如IO时)会堵塞掉全体程序

 

协程定义或正式(满意1,2,3就可称之为协程):

  1. 总得在只有2个单线程里达成产出

  2. 修改共享数据不需加锁

  3. 用户程序里同甘共苦保留几个控制流的前后文栈

  4. 二个体协会程境遇IO操作自动切换来别的协程

    “上下文”,指的是程序在实施中的3个气象。通常咱们会用调用栈来表示那个地方——栈记载了各个调用层级执行到何地,还有执行时的条件处境等有着有关的音讯。

    “上下文切换”,表明的是一种从3个上下文切换成另一个上下文执行的技能。而“调度”指的是决定哪些上下文能够收获接下去的CPU时间的方法。

 

与线程相比:

  1.
python的线程属于基本级其他,即由操作系统控制调度(如单线程一旦遇见io就被迫交出cpu执行权限,切换别的线程运转)

  2.
单线程内打开协程,一旦遇见io,从应用程序级别(而非操作系统)控制切换

 

相比较之下操作系统控制线程的切换,用户在单线程内决定协程的切换,优点如下:

  1.
 协程的切换开支更小,属于程序级其余切换,操作系统完全感知不到,因此特别轻量级

  2. 单线程内就足以达成产出的法力,最大限度地动用cpu

 

用yield生成器函数实现单线程下保存程序的运市场价格况:

import time

def consumer():
    r = ''
    while True:
        n = yield r
        print('[CONSUMER] ←← Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'

def produce(c):
    next(c)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] →→ Producing %s...' % n)
        cr = c.send(n)  #  cr="200 ok"
        print('[PRODUCER] Consumer return: %s' % cr)
    c.close()

if __name__=='__main__':
    c=consumer()  # c:生成器对象
    produce(c)

 

3.2 greenlet类完毕协程

  greenlet机制的严重性思想是:生成器函数大概协程函数中的yield语句挂起函数的实践,直到稍后使用next()或send()操作进行回复结束。能够选用一个调度器循环在一组生成器函数之间合营多少个职责。greentlet是python中落实大家所谓的”Coroutine(协程)”的二个基础库.

 

用greenlet类完结协程举例:

from greenlet import greenlet

def test1():
    print (12)
    gr2.switch()
    print (34)
    gr2.switch()

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

gr1 = greenlet(test1)
gr2 = greenlet(test2)

gr1.switch()

>>:12
    56
    34
    78  

 

3.3 基于greenlet类用 gevent模块实现协程

  Python通过yield提供了对协程的大旨扶助,可是不完全。而第壹方的gevent为Python提供了相比较完善的协程协理。

gevent是第②方库,通过greenlet达成协程,当中央考虑是:

  当一个greenlet蒙受IO操作时,比如访问互连网,就活动切换成别的的greenlet,等到IO操作完毕,再在适度的时候切换回来继续执行。由于IO操作尤其耗费时间,通常使程序处于等候状态,有了gevent为大家自行切换协程,就保证总有greenlet在运维,而不是等待IO。

  由于切换是在IO操作时自动实现,所以gevent要求修改Python自带的片段标准库,这一进度在运维时通过monkey
patch完毕:

  

用gevent模块完毕爬虫

from gevent import monkey
monkey.patch_all()
import requests,gevent,time

def foo(url):
    respnse=requests.get(url)
    respnse_str=respnse.text
    print("GET data %s"%len(respnse_str))

s=time.time()
gevent.joinall([gevent.spawn(foo,"https://itk.org/"),
                gevent.spawn(foo, "https://www.github.com/"),
                gevent.spawn(foo, "https://baidu.com/")])

print(time.time()-s)

上例中仍是能够用gevent.sleep(2)来效仿gevent能够识其他i/o阻塞

而time.sleep(2)或别的的围堵
gevent是不可能直接识别的,须求添加补丁,添加补丁代码如下:

from gevent import monkey
monkey.patch_all()

补丁代码必须放在导入别的模块此前,及位于文件开首

 

附:用进度池、四线程、协程爬虫时间比较

4858.com 14858.com 2

from gevent import monkey
monkey.patch_all()
import requests
import re
from multiprocessing import Pool
import time,threading
import gevent

def getpage(res):
    response_str=requests.get(res)
    print('ecdoing is :',response_str.encoding)
    return response_str.text

def js(ret):
    li=[]
    for item in ret:
        dic={'title':item[2],'date':item[1],'评论数':item[0]}
        li.append(dic)
    f=open('acfun.txt','a',encoding='utf-8')
    for i in li:
        f.write(str(i))
        f.write('\n')
    f.close()

def run(n):
    url='http://www.acfun.cn/v/list73/index_%s.htm'%n
    print(url)
    response=getpage(url)
    # response=response.encode('ISO-8859-1').decode('utf-8')
    obj=re.compile('(\d+).*?<a href=.*? target=".*?" title="发布于 (.*?)" class="title">(.*?)</a>',re.S)
    # obj = re.compile(r'<img.*?src=.(\S+\.jpg).*?', re.S)
    ret=obj.findall(response)
    # print(ret)
    return js(ret)


if __name__ == '__main__':

    start_time=time.time()

    #顺序执行
    # start_time=time.time()
    # for j in range(1,100):
    #     run(j)
    # #顺序执行cost time: 51.30734419822693

    #多线程并发执行
    # li=[]
    # for j in range(1,100):
    #     j = threading.Thread(target=run, args=(j,))
    #     j.start()
    #     li.append(j)
    # for obj in li:
    #     obj.join()
    # 并发执行不使用join cost time: 0.20418000221252441
    # 并发执行使用join cost time: 4.524945974349976

    #使用进程池
    # p = Pool(5)
    # for i in range(1,100):
    #     p.apply_async(func=run,args=(i,))
    # p.close()
    # p.join()
    #使用进程池cost time: 6.876262426376343

    #使用协程
    li = []
    for i in range(1, 100):
        li.append(gevent.spawn(run, i))
    gevent.joinall(li)
    #使用协程第一次cost time: 4.432950973510742
    #使用协程第二次cost time: 30.864907264709473
    #使用协程第三次cost time: 13.472567558288574


    end_time=time.time()
    print('cost time:', end_time-start_time)

使用八线程、进程池、协程爬虫时间比较

 

洗礼灵魂,并发爬网页。一、概念

  协程,又称微线程,纤程。英文名Coroutine。一句话表达哪些是线程:协程是一种用户态的轻量级线程

  协程拥有自个儿的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到另内地方,在切回到的时候,苏醒原先封存的寄存器上下文和栈。因而:

协程能保存上一遍调用时的情形(即具有片段景况的一个特定组合),每便经过重入时,就一定于进入上一次调用的场地,换种说法:进入上1回离开时所处逻辑流的地方。

  协程的益处:

  • 无需线程上下文切换的花费
  • 无须原子操作锁定及共同的支付
    • “原子操作(atomic
      operation)是不必要synchronized”,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开端,就一向运转到甘休,中间不会有别的context switch
      (切换成另叁个线程)。原子操作可以是多少个手续,也足以是多个操作步骤,可是其顺序是不可能被打乱,也许切割掉只进行部分。视作全体是原子性的中坚。
  • 方便人民群众切换控制流,简化编制程序模型
  • 高并发+高扩张性+低本钱:一个CPU协理上万的协程都寻常。所以很吻合用来高并发处理。

  缺点:

  • 不知所厝运用多核实资金源:协程的精神是个单线程,它不能够同时将 单个CPU
    的八个核用上,协程须要和经过合作才能运作在多CPU上.当然我们常见所编纂的多方面采取都并未那一个供给,除非是cpu密集型应用。
  • 展开围堵(Blocking)操作(如IO时)会卡住掉全体程序。

1.定义

协程,顾名思义,程序协商着运营,并非像线程那样争抢着运转。协程又叫微线程,一种用户态轻量级线程。协程正是3个单线程(二个剧本运营的都以单线程)

 协程拥有和谐的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到任何地方,在切回到的时候,复苏原先保存的寄存器上下文和栈。

协程能保留上三次调用时的情况(即具备片段意况的1个一定组合),每便经过重入时,就一定于进入上1遍调用的事态,换种说法:进入上1遍离开时所处逻辑流的岗位,看到那

4858.com 3 

4858.com 4

 4858.com 5

 

不错,就是生成器,前面再实例更会尽量的利用到生成器,但只顾:生成器 !=
协程

 

 

1、yield落到实处协程

import time


def consumer(name):
    print("--->starting eating baozi...")
    while True:
        new_baozi = yield   # yield设置生成器
        print("[{0}] is eating baozi {1}".format(name, new_baozi))


def producer():
    r = con.__next__()  # 调用生成器
    r = con2.__next__()
    n = 0
    while n < 5:
        n += 1
        con.send(n)  # 唤醒生成器,并且向生成器传值
        con2.send(n)
        time.sleep(1)
        print("\033[32m[producer]\033[0m is making baozi {0}".format(n))

if __name__ == '__main__':
    con = consumer("c1")   # 创建一个生成器c1
    con2 = consumer("c2")   # 创建一个生产器C2
    p = producer()

一 、send有五个职能?

  ①唤醒生产器
②给yield传1个值,就是yield接收到的那个值。那个表明yield在被唤醒的时候能够接收数据。

贰 、怎么落到实处大家的单线程达成产出的功能啊?

  境遇IO操作就切换,IO相比较耗费时间,协程之所以能处理大出现,正是IO操作会挤掉大量的时间。没有IO操作的话,整个程序唯有cpu在运算了,因为cpu一点也不慢,所以您觉得是在产出执行的。

③ 、IO操作达成了,程序如何时候切回到?

  IO操作一旦形成,大家就自动切回去。

4、IO是什么?

Python中的io模块是用来处理各类别型的I/O操作流。首要有三种档次的I/O类型:文本I/O(Text
I/O),二进制I/O(Binary I/O)和原始I/O(Raw
I/O)。它们都以通用项目,各类都有两样的后备存款和储蓄。属于这几个品种中的任何2个的切实可行目的称为文件对象,别的常用的术语为流或然类公事对象。

  除了它的花色,每种具体的流对象也装有种种成效:它可是允许读,或许唯有允许写,恐怕既能读又能写。它也同意私自自由走访(向前依然向后搜索此外任务),恐怕仅仅顺序访问(例如在套接字或管道中)。

  全部的流对于提要求它们的数码的数据类型都很严酷。例如,假设用三个二进制流的write()方法写一个字符类型的数额,那么将会触发八个TypeError错误。用文本流的write()方法来写字节对象数据也是相同的,会触发该错误。

 

四、I/O模型

Linux环境下的network IO Model分为:

  •     blocking IO
  •     nonblocking IO
  •     IO multiplexing
  •     signal driven IO
  •     asynchronous IO

出于signal driven IO在其实中并不常用,所以我那只提及剩下的各个IO
Model。
再说一下IO产生时提到的靶子和手续。
  对于1个network IO
(那里咱们以read举例),它会涉及到多个系统对象,3个是调用那个IO的process
(or
thread),另1个便是系统基本(kernel)。当三个read操作发生时,它会经历多少个级次:

  •  等待数据准备 (Waiting for the data to be ready)
  •  将数据从基本拷贝到进度中 (Copying the data from the kernel to the
    process)

难忘这两点很重庆大学,因为这个IO Model的区别正是在四个阶段上各有分歧的意况。

② 、yield达成协程

2.特性

优点:

  • 无需线程上下文切换的费用
  • 无须原子操作锁定及联合的支出
  • 方便切换控制流,简化编制程序模型
  • 高并发+高增添性+低本钱:三个CPU支持上万的协程都小难题。所以很合乎用来高并发处理。

注:比如修改多少个数额的满贯操作进度下来只有四个结果,要嘛已修改,要嘛未修改,中途出现任何不当都会回滚到操作前的动静,那种操作情势就叫原子操作,”原子操作(atomic
operation)是不需求synchronized”,不会被线程调度机制打断的操作;那种操作一旦开头,就直接运转到告竣,中间不会有别的context switch
(切换成另多少个线程)。原子操作能够是2个手续,也能够是八个操作步骤,可是其顺序是不得以被打乱,也许切割掉只进行部分。视作全体是原子性的主干。 

 

缺点:

  • 胸中无数使用多核实资金源:协程的本色是个单线程,它不能够同时将 单个CPU
    的多少个核用上,协程须要和过程同盟才能运作在多CPU上.当然咱们常见所编纂的多方面选用都未曾这些要求,除非是cpu密集型应用。
  • 进行围堵(Blocking)操作(如IO时)会卡住掉全体程序

二 、手动完毕切换IO

Greenlet是python的一个C扩展,来源于Stackless
python,目的在于提供可活动调度的‘微线程’,
即协程。它可以使您在任意函数之间自由切换,而不需把那几个函数先评释为generator

from greenlet import greenlet


def test1():
    print(12)
    gr2.switch()  # 切换到test2
    print(34)
    gr2.switch()   # 切换到test2


def test2():
    print(56)
    gr1.switch()   # 切换到test1
    print(78)

gr1 = greenlet(test1)  # 启动一个协程
gr2 = greenlet(test2)
gr1.switch()   # 切换到test1,这个switch不写的话,会无法输出打印

#执行结果
12
56
34
78

小结:

  1. cpu值认识线程,而不认识协程,协程是用户本人控制的,cpu根本都不驾驭它们的存在。
  2. 线程的上下文切换保存在cpu的寄存器中,但是协程拥有自身的存放上下文和栈。
  3. 协程是串行的,无需锁。

尽管如此greenlet确实用着比generator(生成器)还简要了,但就像还没有化解3个标题,就是蒙受IO操作,自动切换,对不对?

4.1 blocking IO (阻塞IO)

在linux中,暗许情状下全部的socket都以blocking,3个卓越的读操作流程大概是如此:

4858.com 6

  当用户进度调用了recvfrom这一个类别调用,kernel就初阶了IO的首先个级次:准备数据。对于network
io来说,很多时候数据在一开始还没有到达(比如,还未曾收取1个完全的UDP包),这些时候kernel就要等待丰硕的多少来临。而在用户进程那边,整个经过会被卡住。当kernel一贯等到数量准备好了,它就会将数据从kernel中拷贝到用户内部存款和储蓄器,然后kernel重返结果,用户进度才解除block的情景,重国民党的新生活运动行起来。

blocking IO的性状:在IO执行的八个级次都被block了,全程阻塞

 

 

2.壹 、yield完成协程

import time

def consumer(name):
    print("--->starting eating baozi...")
    while True:
        new_baozi = yield   #yield设置生成器
        print("[{0}] is eating baozi {1}".format(name,new_baozi))

def producer():
    r = con.__next__()#调用生成器
    r = con2.__next__()
    n = 0
    while n < 5:
        n +=1
        con.send(n)  #唤醒生成器,并且向生成器传值
        con2.send(n)
        time.sleep(1)
        print("\033[32m[producer]\033[0m is making baozi {0}".format(n))

if __name__ == '__main__':
    con = consumer("c1")   #创建一个生成器c1
    con2 = consumer("c2")   #创建一个生产器C2
    p = producer()

问题:

壹 、send有三个成效?

  ①唤醒生产器
②给yield传四个值,正是yield接收到的那几个值。这几个表明yield在被唤醒的时候能够接收数据。

贰 、怎么落到实处大家的单线程完结产出的成效呢?

  境遇IO操作就切换,IO比较耗时,协程之所以能处理大产出,正是IO操作会挤掉大批量的岁月。没有IO操作的话,整个程序唯有cpu在运算了,因为cpu不慢,所以您觉得是在出现执行的。

三 、IO操作达成了,程序如哪一天候切回到?

  IO操作一旦成功,我们就自动切回去。

 

③ 、协程遇IO操作自动切换

下来就说说怎么相遇IO就自动切换切换,Gevent
是八个第一方库,能够轻松通过gevent达成产出同步或异步编程,在gevent中用到的机要情势是Greenlet,
它是以C扩展模块格局接入Python的轻量级协程。
格林let全体运行在主程序操作系统进度的当中,但它们被合作式地调度。

import gevent


def foo():
    print("Running in foo")
    gevent.sleep(3)  # 模仿io操作,一遇到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 fun3():
    print("running fun3")
    gevent.sleep(0)   # 虽然是0秒,但是会触发一次切换
    print("running fun3 again")

gevent.joinall([
    gevent.spawn(foo),  # 生成协程
    gevent.spawn(bar),
    gevent.spawn(fun3)
])

#执行结果
Running in foo
Explicit context to bar
running fun3
running fun3 again
Implicit context switch back to bar
Explicit context switch to foo again

当foo蒙受sleep(2)的时候,切自动切换来bar函数,执行碰到sleep(1)的时候自动切换来fun3函数,碰到sleep(0)又自动切换成foo。这一个时候sleep(2)还一贯不实施实现,又切换来bar的sleep(1)这边,发现又尚未举行实现,就有实行fun3那边,发现sleep(0)执行完成,则继续执行,然后又切换到foo,发现sleep(2)又从不实施完结,就切换来bar的sleep(1)那边,发现执行完了,有切回到foo那边,执行完结。

重点成效:比如说你现在又50处IO,然后一起加起来串行的来说,要花100秒,可是50处IO最长的那几个IO只花了5分钟,那表示中您的那几个程序便是协程最多5秒就举办完结了。

顺应下边三个规范才能称为协程:

  1. 务必在唯有多个单线程里完结产出
  2. 修改共享数据不需加锁
  3. 用户程序里和衷共济保留多少个控制流的上下文栈
  4. 3个体协会程蒙受IO操作自动切换成别的协程

 

 

4.2 non-blocking IO(非阻塞IO)

linux下,能够经过设置socket使其变成non-blocking。当对1个non-blocking
socket执行读操作时,流程是其一样子:

4858.com 7

  从图中能够看来,当用户进度产生read操作时,即便kernel中的数据还尚未备选好,那么它并不会block用户进度,而是马上回到四个error。从用户进度角度讲
,它提倡3个read操作后,并不必要等待,而是立时就获得了三个结出。用户进度判断结果是二个error时,它就明白多少还未曾防患未然好,于是它能够重复发送read操作。一旦kernel中的数据准备好了,并且又再次接受了用户进度的system
call,那么它立即就将数据拷贝到了用户内部存款和储蓄器,然后回到。所以,用户进度实际是急需不断的能动询问kernel数据好了并未。

 

优点:能够在等待职分达成的时光里干任何活了(包罗提交其他职分,也便是“后台” 能够有七个职责在同时执行)。

缺点:任务成功的响应延迟增大了,因为每过一段时间才去轮询一回read操作,而任务也许在两回轮询之间的任性时间完结。那会促成全体数据吞吐量的下挫。

 

叁 、手动实现切换IO

3.实例

协程(gevent)并发爬网页

地点例子gevent遭遇io自动切换,现在就来其实演示协程爬虫的例子

4.3 IO multiplexing(IO多路复用)

   IO
multiplexing那么些词大概有点素不相识,不过倘使作者说select,epoll,大约就都能明白了。有个别地点也称那种IO格局为event
driven
IO。大家都知道,select/epoll的利益就在于单个process就能够而且处理两个网络连接的IO。它的基本原理正是select/epoll这么些function会不断的轮询所承受的有所socket,当某些socket有数量到达了,就通报用户进程。它的流程如图:

 4858.com 8

  当用户进程调用了select,那么万事进度会被block,而与此同时,kernel会“监视”全体select负责的socket,当其他多少个socket中的数据准备好了,select就会回去。那些时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
  这一个图和blocking
IO的图其实并不曾太大的不等,事实上,还更差那么一点。因为此地必要选取七个system
call (select 和 recvfrom),而blocking IO只调用了1个system call
(recvfrom)。但是,用select的优势在于它能够同时处理多少个connection

  (所以,固然拍卖的连接数不是很高的话,使用select/epoll的web
server不一定比采取multi-threading + blocking IO的web
server品质更好,也许推迟还更大。select/epoll的优势并不是对此单个连接能处理得更快,而是在乎能处理更加多的连接。)
  在IO multiplexing
Model中,实际中,对于每1个socket,一般都安装成为non-blocking,不过,如上海教室所示,整个用户的process其实是一贯被block的。只然则process是被select那些函数block,而不是被socket
IO给block。

结论:
select的优势在于能够拍卖三个延续,不适用于单个连接
 

3.① 、greenlet达成手动切换

申明:通过自带方法swith去手动切换IO

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()  #切换到test2
    print(34)   
    gr2.switch()   #切换到test2

def test2():
    print(56)
    gr1.switch()   #切换到test1
    print(78)

gr1 = greenlet(test1)  #启动一个协程
gr2 = greenlet(test2)
gr1.switch()   #切换到test1 

实践的手续如下:

4858.com 9

因而实行的结果如下:

#输出
12
56
34
78

 注意了:gr1.switch(),它今后不是赶上IO就切换,便是你手动切换,就如刚刚所讲的yield,next一下,便是跟那么些意思大概,正是切换一下。

 1)用生成器完成伪协程:

在那后边,相信广大恋人早就把生成器是怎么着忘了啊,那里大约复习一下。

创办生成器有四个放法:

A:使用列表生成器:

4858.com 10

 

B:使用yield创立生成器:

4858.com 11

 

做客生成器数据,使用next()或然__next__()方法:

4858.com 12

 

好的,既然说到此地,就说下,yield能够暂存数据并转载:

4858.com 13

 

传是传入了,但结果却报错:

4858.com 14

 

为什么报错呢?首先要说1个知识点,行使next()和send()方法都会取出1个数码,差异的是send即发送数据又取出上一数额,并且只要要发送数据必须是第三遍发送,就算第2回正是用send,必须写为send(None)才行,不然报错。next(obj) = obj.send(None).

因为yield是暂存数据,每趟next()时将会在截至时的此处阻塞住,下一遍又从那里早先,而发送完,send取数据发现早已甘休了,数据已经没了,所以修改报错,

那么稍作修改得:

4858.com 15

 

完美!

 

好的,进入正题了,有了地点的现金,现在现卖应该没难点了:

照旧是前方的劳动者消费者模型 

import time
import queue

def consumer(name):
    print("--->starting eating baozi...")
    while True:
        new_baozi = yield
        print("[%s] is eating baozi %s" % (name,new_baozi))
        #time.sleep(1)

def producer():

    r = con.__next__()
    r = con2.__next__()
    n = 0
    while n < 5:
        n +=1
        con.send(n)
        con2.send(n)
        print("\033[32;1m[producer]\033[0m is making baozi %s" %n )


if __name__ == '__main__':
    con = consumer("c1")
    con2 = consumer("c2")
    p = producer()

  

运行结果:

4858.com 16

 

第3大家知道使用yield创立了2个生成器对象,然后每便使用时采取new_baozi做一个中间转播站来缓存数据。这便是达成协程效果了对啊?

近期小编提了一句,yield下是伪协程,那么什么样是真的的协程呢?

亟需全数以下条件

  • 总得在唯有二个单线程里实现产出
  • 修改共享数据不需加锁
  • 三个体协会程境遇IO操作自动切换来别的协程
  • 用户程序里相濡相呴保留四个控制流的上下文栈

 

1、正常(串行)爬网页

串行效果的爬网页的代码,看看消耗多久

from urllib import request
import time


def run(url):
    print("GET:{0}".format(url))
    resp = request.urlopen(url)    # request.urlopen()函数 用来打开网页
    data = resp.read()    # 读取爬到的数据
    with open("url.html", "wb") as f:
        f.write(data)
    print('{0} bytes received from {1}'.format(len(data), url))

urls = [
    'http://www.163.com/',
    'https://www.yahoo.com/',
    'https://github.com/'
]

time_start = time.time()    # 开始时间
for url in urls:
    run(url)
print("同步cost", time.time() - time_start)  # 程序执行消耗的时间

#执行结果
GET:http://www.163.com/
659094 bytes received from http://www.163.com/
GET:https://www.yahoo.com/
505819 bytes received from https://www.yahoo.com/
GET:https://github.com/
56006 bytes received from https://github.com/
同步cost 4.978517532348633

  

 4.4 Asynchronous I/O(异步IO)

 linux下的asynchronous IO其实用得很少。先看一下它的流程: 

 4858.com 17

  用户进度发起read操作之后,立刻就足以开端去做别的的事。而一方面,从kernel的角度,当它受到三个asynchronous
read之后,首先它会即时回去,所以不会对用户进度产生任何block。然后,kernel会等待数据准备实现,然后将数据拷贝到用户内部存款和储蓄器,当这一体都成功之后,kernel会给用户进程发送三个signal,告诉它read操作达成了。

四、总结

  1. cpu值认识线程,协程cpu是不认识的,是用户自个儿支配的,cpu根本都不知道它们的存在。
  2. 线程的上下文切换保存在cpu的寄存器中,然而协程拥有和谐的寄放上下文和栈。
  3. 协程是串行的,无需锁。

符合协程的口径:

  1. 务必在唯有三个单线程里实现产出
  2. 修改共享数据不需加锁
  3. 用户程序里和衷共济保留多少个控制流的上下文栈
  4. 贰个协程遭受IO操作自动切换成别的协程

2)gevent协程

首先其实python提供了三个正经库Greenlet正是用来搞协程的

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

from greenlet import greenlet

def test1():
    print(1)
    gr2.switch() #switch方法作为协程切换
    print(2)
    gr2.switch()

def test2():
    print(3)
    gr1.switch()
    print(4)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

  

运作结果:

4858.com 18

 

可是意义不佳,不可能满足IO阻塞,所以一般景色都用第③方库gevent来落到实处协程:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import gevent,time

def test1():
    print(1,time.ctime())
    gevent.sleep(1)     #模拟IO阻塞,注意此时的sleep不能和time模块下的sleep相提并论
    print(2,time.ctime())

def test2():
    print(3,time.ctime())
    gevent.sleep(1)
    print(4,time.ctime())

gevent.joinall([
    gevent.spawn(test1), #激活协程对象
    gevent.spawn(test2)
])

  

运作结果:

4858.com 19

 

那正是说只要函数带有参数怎么搞呢?

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import gevent


def test(name,age):
    print('name:',name)
    gevent.sleep(1)     #模拟IO阻塞
    print('age:',age)


gevent.joinall([
    gevent.spawn(test,'yang',21), #激活协程对象
    gevent.spawn(test,'ling',22)
])

  

运行结果:

4858.com 20

 

 如若你对那个体协会程的速度觉得不完美,能够加上上面这一段,别的不变:4858.com 21

 

 这个patch_all()约等于2个检查和测试机制,发现IO阻塞就及时切换,不需静观其变什么。那样能够节约一些时刻

 

 好的,协程解析实现。

 

 2、协程(gevent)爬虫

用gevent并发执行一下,看看效果。

from urllib import request
import gevent,time

def run(url):
    print("GET:{0}".format(url))
    resp = request.urlopen(url)    # request.urlopen()函数 用来打开网页
    data = resp.read()    # 读取爬到的数据
    with open("url.html", "wb") as f:
        f.write(data)
    print('{0} bytes received from {1}'.format(len(data), url))

urls = [
    'http://www.163.com/',
    'https://www.yahoo.com/',
    'https://github.com/'
]

time_start = time.time()    # 开始时间
gevent.joinall([                     # 用gevent启动协程
    gevent.spawn(run, 'http://www.163.com/'),  # 第二个值是传入参数,之前我们没有讲,因为前面没有传参
    gevent.spawn(run, 'https://www.yahoo.com/'),
    gevent.spawn(run, 'https://github.com/'),
])
print("同步cost", time.time() - time_start)  # 程序执行消耗的时间

#执行结果
GET:http://www.163.com/
659097 bytes received from http://www.163.com/
GET:https://www.yahoo.com/
503844 bytes received from https://www.yahoo.com/
GET:https://github.com/
55998 bytes received from https://github.com/
同步cost 4.433035850524902

相比较壹 、2爬网页的例子,发现实施耗时上并从未得到显然提高,并没有出现爬网页的神奇快感,其实主倘使因为gevent以往检查和测试不到urllib的IO操作。它都不明了urllib举办了IO操作,感受不到过不去,它都不会进展切换,所以它就串行了。

4.5 IO模型比较分析

 各类IO Model的可比如图所示:

 4858.com 22

 

4.6 selectors模块

import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # 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', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

  

 

叁 、打个补丁,告诉gevent,urllib正在开始展览IO操作

通过导入monkey模块,来打那么些补丁,原代码不变,就添加一行monkey.patch_all()即可。

from urllib import request
import gevent,time
from gevent import monkey  # 导入monkey模块

monkey.patch_all()  # 把当前程序的所有的IO操作给作上标记


def run(url):
    print("GET:{0}".format(url))
    resp = request.urlopen(url)    # request.urlopen()函数 用来打开网页
    data = resp.read()    # 读取爬到的数据
    with open("url.html", "wb") as f:
        f.write(data)
    print('{0} bytes received from {1}'.format(len(data), url))

urls = [
    'http://www.163.com/',
    'https://www.yahoo.com/',
    'https://github.com/'
]

time_start = time.time()    # 开始时间
gevent.joinall([                     # 用gevent启动协程
    gevent.spawn(run, 'http://www.163.com/'),  # 第二个值是传入参数,之前我们没有讲,因为前面没有传参
    gevent.spawn(run, 'https://www.yahoo.com/'),
    gevent.spawn(run, 'https://github.com/'),
])
print("同步cost", time.time() - time_start)  # 程序执行消耗的时间


#执行结果
GET:http://www.163.com/
GET:https://www.yahoo.com/
GET:https://github.com/
659097 bytes received from http://www.163.com/
503846 bytes received from https://www.yahoo.com/
55998 bytes received from https://github.com/
同步cost 1.8789663314819336

原本临近5秒的耗费时间现行反革命只用了不到2秒就落成,这正是协程的魅力,通过打补丁来检查和测试urllib,它就把urllib里面装有涉嫌到的有恐怕进行IO操作的地方直接花在头里加贰个标记,那几个标记就一定于gevent.sleep(),所以把urllib变成贰个一有梗塞,它就切换了

 

④ 、gevent实现单线程下的多socket并发

4.1、server端

import sys,gevent,socket,time
from gevent import socket,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()
        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(8888)

  

4.2、client端

import socket

HOST = 'localhost'    # The remote host
PORT = 8888           # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"),encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)
    print('Received', repr(data))
s.close()

  

 

 

 

 

 

 

 

 

 

 

 

 

4858.com, 

 

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有