亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

python中的協程深入理解

系統 1620 0

先介紹下什么是協程:

  協程,又稱微線程,纖程,英文名Coroutine。協程的作用,是在執行函數A時,可以隨時中斷,去執行函數B,然后中斷繼續執行函數A(可以自由切換)。但這一過程并不是函數調用(沒有調用語句),這一整個過程看似像多線程,然而協程只有一個線程執行。

是不是有點沒看懂,沒事,我們下面會解釋。要理解協程是什么,首先需要理解yield,這里簡單介紹下,yield可以理解為生成器,yield item這行代碼會產出一個值,提供給next(...)的調用方; 此外,還會作出讓步,暫停執行生成器,讓調用方繼續工作,直到需要使用另一個值時再調用next()。調用方會從生成器中拉取值,但是在協程中,yield關鍵字一般是在表達式右邊(如,data=yield),協程可以從調用方接收數據,也可以產出數據,下面看一個簡單的例子:

            
>>> def simple_coroutine():
...  print('coroutine start')
...  x = yield
...  print('coroutine recive:',x)
...  
>>> my_co=simple_coroutine()
>>> my_co

            
              
>>> next(my_co)
coroutine start
>>> my_co.send(42)
coroutine recive: 42
Traceback (most recent call last):
 File "
              
              ", line 1, in 
              
                
StopIteration
              
            
          

其中x = yield就是精髓部分,意思是從客戶端獲取數據,產出None,因為yield關鍵字右邊沒有表達式, 而協程在創建完成之后,是沒有啟動的,沒有在yield處暫停,所以需要調用next()函數,啟動協程,在調用my_co.send(42)之后,協程定義體中的yield表達式會計算出42,現在協程恢復,一直運行到下一個yield表達式,或者終止,在最后,控制權流動到協程定義體的末尾,生成器拋出StopIteration異常。

協程有四個狀態,如下:

  • 'GEN_CREATED' 等待開始執行。
  • 'GEN_RUNNING' 解釋器正在執行。
  • 'GEN_SUSPENDED' 在 yield 表達式處暫停。
  • 'GEN_CLOSED' 執行結束。

當前狀態可以使用inspect.getgeneratorstate來確定,如下:

            
>>> import inspect
>>> inspect.getgeneratorstate(my_co)
'GEN_CLOSED'
          

這里再解釋下next(my_co),如果在創建好協程對象之后,立即把None之外的值發送給它,會出現如下錯誤:

            
>>> my_co=simple_coroutine()
>>> my_co.send(42)
Traceback (most recent call last):
 File "
            
            ", line 1, in 
            
              
TypeError: can't send non-None value to a just-started generator
>>> my_co=simple_coroutine()
>>> my_co.send(None)
coroutine start
            
          

最先調用 next(my_co) 函數這一步通常稱為“預激”(prime)協程(即,讓協程向前執行到第一個 yield 表達式,準備好作為活躍的協程使用)。

再參考下面這個例子:

            
>>> def simple_coro2(a):
...  print('-> Started: a =', a)
...  b = yield a
...  print('-> Received: b =', b)
...  c = yield a + b
...  print('-> Received: c =', c)
...  
>>> my_coro2 = simple_coro2(14)
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(my_coro2)
'GEN_CREATED'
>>> next(my_coro2) # 協程執行到`b = yield a`處暫停,等待為b賦值,
-> Started: a = 14
14
>>> getgeneratorstate(my_coro2) 
'GEN_SUSPENDED' #從狀態也可以看到,當前是暫停狀態。
>>> my_coro2.send(28) #將28發送到協程,計算yield表達式,并把結果綁定到b,產出a+b的值,然后暫停。
-> Received: b = 28
42
>>> my_coro2.send(99)
-> Received: c = 99
Traceback (most recent call last):
 File "
            
            ", line 1, in 
            
              
StopIteration
>>> getgeneratorstate(my_coro2)
'GEN_CLOSED'
            
          

simple_coro2的執行過程如下圖所示:

python中的協程深入理解_第1張圖片

  • 調用next(my_coro2),打印第一個消息,然后執行yield a,產出數字 14。
  • 調用my_coro2.send(28),把28賦值給b,打印第二個消息,然后執行yield a + b,產 出數字 42。
  • 調用my_coro2.send(99),把 99 賦值給 c,打印第三個消息,協程終止。

說了這么多,我們為什么要用協程呢,下面我們再看看它的優勢是什么:

  • 執行效率極高,因為子程序切換(函數)不是線程切換,由程序自身控制,沒有切換線程的開銷。所以與多線程相比,線程的數量越多,協程性能的優勢越明顯。
  • 不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在控制共享資源時也不需要加鎖,因此執行效率高很多。

說明:協程可以處理IO密集型程序的效率問題,但是處理CPU密集型不是它的長處,如要充分發揮CPU利用率可以結合多進程+協程。

下面看最后一個例子,傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖。

如果改用協程,生產者生產消息后,直接通過yield跳轉到消費者開始執行,待消費者執行完畢后,切換回生產者繼續生產,效率極高:

            
from bs4 import BeautifulSoup
import requests
from urllib.parse import urlparse

start_url = 'https://www.cnblogs.com'
trust_host = 'www.cnblogs.com'
ignore_path = []
history_urls = []


def parse_html(html):
  soup = BeautifulSoup(html, "lxml")
  print(soup.title)
  links = soup.find_all('a', href=True)
  return (a['href'] for a in links if a['href'])


def parse_url(url):
  url = url.strip()

  if url.find('#') >= 0:
    url = url.split('#')[0]
  if not url:
    return None
  if url.find('javascript:') >= 0:
    return None

  for f in ignore_path:
    if f in url:
      return None
  if url.find('http') < 0:
    url = start_url + url
    return url
  parse = urlparse(url)
  if parse.hostname == trust_host:
    return url


def consumer():
  html = ''
  while True:
    url = yield html
    if url:
      print('[CONSUMER] Consuming %s...' % url)
      rsp = requests.get(url)
      html = rsp.content


def produce(c):
  next(c)

  def do_work(urls):
    for u in urls:
      if u not in history_urls:
        history_urls.append(u)
        print('[PRODUCER] Producing %s...' % u)
        html = c.send(u)
        results = parse_html(html)
        work_urls = (x for x in map(parse_url, results) if x)
        do_work(work_urls)

  do_work([start_url])
  c.close()


if __name__ == '__main__':
  c = consumer()
  produce(c)
  print(len(history_urls))
          

首先consumer函數是一個generator,在開始執行之后:

  1. 調用next(c)啟動生成器;
  2. 進入do_work,這是一個遞歸調用,其內部將url傳遞給consumer,由consumer來發出請求,獲取到html信息,返回給produce,
  3. produce解析html,獲取url數據,繼續生產url,
  4. 當所有的url都在history_urls中,也就是說我們已經爬取了所有的url地址,結束遞歸調用
  5. 調用c.close(),關閉consumer,整個過程結束。

可以看到,我們的整個流程無鎖,由一個線程執行,produce和consumer協作完成任務,所以稱為“協程”,而非線程的搶占式多任務。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产一级特黄生活片 | 国产精品在线观看 | 婷婷五 在线播放 | 久热re国产手机在线观看 | 中文字幕日本在线 | 亚洲国产天堂 | 一区二区在线精品免费视频 | 另类图片综合 | 午夜视频久久 | 五月天天色 | 久久青青草原精品影院 | 久久天天躁狠狠躁夜夜躁综合 | 福利在线播放 | 一级特黄aa毛片免费观看 | 欧美成人一区二免费视频 | 婷婷激情四月 | 久草精品视频在线播放 | 色综合啪啪 | 久久久久依人综合影院 | 欧美狠狠干 | 婷婷免费高清视频在线观看 | 亚洲国产www | 一 级 黄 色蝶 片 | 深夜在线观看 | 亚洲精品福利一区二区三区 | 日韩国产欧美成人一区二区影院 | 国产精品边做奶水狂喷小说 | 9久久这里只有精品国产 | 国产一区二区在线观看视频 | 国产成人精品区在线观看 | 国产青草亚洲香蕉精品久久 | 日本美女久久 | 中文字幕中文字幕在线 | 国内精品不卡一区二区三区 | 免费国产午夜高清在线视频 | 亚洲久久久 | 一级毛片在线看在线播放 | 妖精视频在线观看网站 | 国产激情久久久久影院小草 | 国产无卡一级毛片aaa | 三级大黄|