python---基礎知識回顧(十)進程和線程(協程gevent:線程在I/O請求上的優化)

優勢:使用gevent協程,能夠更好的利用線程資源。(基於線程實現)

需求:使用一個線程,去請求多個網站的資源(注意,請求上會有延時)<其實是去請求了大量的網站信息,咱們使用了多線程,只不過每一個線程依舊會分配到多個網站資源,這裏咱們只須要去討論這一條線程便可>

能夠看出,因爲網絡延遲等因素,當咱們去獲取信息時,有一段時間唄浪費,用於空等信息返回,當咱們去獲取大量網站的時候,那這個時間是很是大的。咱們須要去避免他。

解決方案:使用協程,充分利用咱們中間等待的這一段時間,去作其餘的事情,好比其請求下一個網站,,或者下幾個網站。而後連續去接收信息,就能夠充分的利用空耗的時間

1.協程的簡單使用:

pip3 install gevent  # gevent模塊如果沒有,只須要先下載

開始使用:網絡

import gevent
from gevent import monkey
monkry.patch_all()  #能夠提升效率
def foo(): print(
"foo函數開始運行") gevent.sleep(0) print("又回到了foo函數") def bar(): print("bar函數開始運行") gevent.sleep(0) print("又回到了bar函數") gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar), ])
輸出結果:
foo函數開始運行 bar函數開始運行 又回到了foo函數 又回到了bar函數

2.協程的瞭解:對於上面的例子來講,有點不太容易理解,咱們使用計時去了解其中流程,再去討論上面代碼

(1)上面sleep(0)和下面的sleep(3)相比,得出兩個函數的執行時間是一致的(幾乎是)

import gevent
import time

begin = time.time()

def foo():
    fs = time.time() - begin
    print("foo函數開始運行",fs)
gevent.sleep(
3) fe = time.time() - begin print("又回到了foo函數",fe) def bar(): bs = time.time() - begin print("bar函數開始運行",bs)
gevent.sleep(
3)
be
= time.time() - begin print("又回到了bar函數",be) gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar), ])
foo函數開始運行 0.01000070571899414
bar函數開始運行 0.01000070571899414
又回到了foo函數 3.0101723670959473
又回到了bar函數 3.0101723670959473

注意輸出結果多線程

咱們能夠看出兩個函數都是在統一時間執行第一句輸出,在三秒後去執行的第二句輸出框架

 (2)sleep(3)和sleep(1)

import gevent
import time

begin = time.time()

def foo():
    fs = time.time() - begin
    print("foo函數開始運行",fs)
gevent.sleep(
1) fe = time.time() - begin print("又回到了foo函數",fe) def bar(): bs = time.time() - begin print("bar函數開始運行",bs)
gevent.sleep(
3)
be
= time.time() - begin print("又回到了bar函數",be) gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar), ])

注意輸出結果:幾乎在同一時間執行兩個函數(順序和joinall方法中註冊順序有關),在咱們設定的sleep時間後去繼續執行函數函數

foo函數開始運行 0.0060002803802490234
bar函數開始運行 0.0060002803802490234
又回到了foo函數 1.0060575008392334
又回到了bar函數 3.006171941757202

因此說對於最上面簡單使用中的執行順序先是根據joinall的註冊順序去打印網站

foo函數開始運行
bar函數開始運行

而後因爲sleep(0)間隔是0,因此當即去執行下面的打印程序(當sleep的時間是一致時,順序仍是和註冊時一致)url

又回到了foo函數
又回到了bar函數

(3)使用time.sleep()去更加深入瞭解協程

 

import gevent
import time

begin = time.time()

def foo():
    fs = time.time() - begin
    print("foo函數開始運行",fs)
gevent.sleep(
1)
time.sleep(
4)  #這裏睡眠4秒 fe = time.time() - begin print("又回到了foo函數",fe) def bar(): bs = time.time() - begin print("bar函數開始運行",bs)
gevent.sleep(
3)
be
= time.time() - begin print("又回到了bar函數",be) gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar), ])

注意輸出結果:發現對於咱們在foo中設置的time.sleep(4)對bar方法也有影響。spa

foo函數開始運行 0.005000114440917969
bar函數開始運行 0.0060002803802490234
又回到了foo函數 5.006286144256592
又回到了bar函數 5.007286310195923

緣由:gevent設置了咱們協程的甦醒時間,可是當甦醒時間與咱們的執行時間相沖突,那麼會以執行時間爲主(畢竟這是單線程,不會考慮其餘的),而原來的設置的gevent.sleep(秒數)則變成了大小比較,誰在後,誰就後執行線程

 任務框架:

import gevent
import time

begin = time.time()

def foo(url,index):
    fs = time.time() - begin
    print("%s:發送請求到%s,等待返回"%(index,url),fs) #這裏能夠模擬發送請求
    gevent.sleep(0)
    fe = time.time() - begin
    print("%s:獲取信息從%s,開始處理"%(index,url),fe) #這裏模擬處理信息

gevent.joinall([
    gevent.spawn(foo,"www.baidu.com",1),  #注意傳參方式
    gevent.spawn(foo,"www.sina.com.sn",2),
])

輸出結果:3d

1:發送請求到www.baidu.com,等待返回 0.005000114440917969
2:發送請求到www.sina.com.sn,等待返回 0.005000114440917969
1:獲取信息從www.baidu.com,開始處理 0.005000114440917969
2:獲取信息從www.sina.com.sn,開始處理 0.005000114440917969


 補充:greenlet協程(gevent是基於greenlet實現,因此有必要去了解下)

from greenlet import greenlet

def foo():
    print("開始執行foo")
    gr2.switch()
    print("又回到foo")
    gr2.switch()

def bar():
    print("開始執行bar")
    gr1.switch()
    print("又回到bar")

gr1 = greenlet(foo)
gr2 = greenlet(bar)
gr1.switch()    #以gr1開始執行,switch中也能夠傳遞參數

輸出結果:code

開始執行foo
開始執行bar
又回到foo
又回到bar
相關文章
相關標籤/搜索