python之路——協程(greenlet、gevent、簡單爬蟲)

  協程  

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程python

協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:編程

協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。併發

協程的好處:異步

  • 無需線程上下文切換的開銷
  • 無需原子操做鎖定及同步的開銷
  • "原子操做(atomic operation)是不須要synchronized",所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另外一個線程)。原子操做能夠是一個步驟,也能夠是多個操做步驟,可是其順序是不能夠被打亂,或者切割掉只執行部分。視做總體是原子性的核心。
  • 方便切換控制流,簡化編程模型
  • 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。

 缺點:async

  • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

使用yield實現協程操做例子異步編程

import time

def consumer(name):
    print("開始吃包子")
    while True:
        bun = yield            #使函數掛起
        print(name,"正在吃",bun)

def producer():
    r = con.__next__()
    r = con2.__next__()
    n = 0
    while n < 5:
        n += 1
        con.send(n)
        con2.send(n)
        time.sleep(2)
        print("正在製做包子",n)

if __name__ == '__main__':
    con = consumer("alex")
    con2 = consumer("japhi")
    p = producer()

協程必須知足如下條件:

  1. 必須在只有一個單線程裏實現併發
  2. 修改共享數據不需加鎖
  3. 用戶程序裏本身保存多個控制流的上下文棧
  4. 一個協程遇到IO操做自動切換到其它協程

  手動切換(Greenlet)  

greenlet是一個用C實現的協程模塊,相比與python自帶的yield,它可使你在任意函數之間隨意切換,而不需把這個函數先聲明爲generator函數

import greenlet
def main():
    print("1111111")
    gr2.switch()
    print("3333333")
    gr2.switch()

def next():
    print("2222222")
    gr1.switch()
    print("4444444")

if __name__ == "__main__":
    gr1 = greenlet.greenlet(main)    #實例化一個協程對象,括號內是函數
    gr2 = greenlet.greenlet(next)
    gr1.switch()   #手動切換

輸出結果:
1111111
2222222
3333333
4444444

先實例化對象,而後調用手動切換方法switch 高併發

  自動切換(Gevent) 

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。atom

import gevent

def first():
    print(1111)
    gevent.sleep(3)   #模擬io切換,自動切換
    print(22222)

def second():
    print(33333)
    gevent.sleep(2)   #模擬io切換,自動切換
    print(44444)
def third():
    print(55555)
    gevent.sleep(1)   #模擬io切換,自動切換
    print(666666)

gevent.joinall([      #須要把函數全都加入進去
    gevent.spawn(first),
    gevent.spawn(second),
    gevent.spawn(third)
])


輸出:
1111
33333
55555
666666
44444
22222

利用gevent實現簡單爬蟲

from urllib import request
import gevent,time
from gevent import monkey
monkey.patch_all()         #添加io操做標準,讓程序能夠並行下載網頁

def net(url):
    resq = request.urlopen(url)
    data = resq.read()
    print(len(data))

urls = ['https://www.baidu.com/',
        'https://www.hao123.com/',
        'https://www.sina.com/' ]
time_start = time.time()
for url in urls:
    net(url)
print("同步cost",time.time() - time_start)

async_time_start = time.time()
gevent.joinall([
    gevent.spawn(net,"https://www.baidu.com"),
    gevent.spawn(net,"https://www.hao123.com"),
    gevent.spawn(net,"https://www.sina.com/")
])
print("異步cost",time.time() - async_time_start)

輸出結果:
227
514581
601335
同步cost 2.6131491661071777
227
514599
601338
異步cost 2.2071263790130615

異步並行所花的時間少於同步,但要加  monkey.patch_all()           #添加io操做標準,讓程序能夠並行下載網頁url

相關文章
相關標籤/搜索