python從yield到asyncio<第五章>

承接python從yield到asyncio<第四章>中提到的代碼問題。稍微修改一下代碼

# -*- coding: utf-8 -*-

# 讀取當當網的圖書
import requests
import aiohttp
import asyncio
from bs4 import BeautifulSoup
import time
import os
from concurrent.futures import ThreadPoolExecutor


# 子生成器
@asyncio.coroutine
def get_image(img_url):
    yield from asyncio.sleep(1)
    resp = yield from aiohttp.request('GET', img_url)
    image = yield from resp.read()
    return image


def save_image(img, img_url):
    time.sleep(0.5)
    with open(os.path.join('./img_file', img_url.split('/')[-1]), 'wb') as f:
        f.write(img)


@asyncio.coroutine
def download_one(img_url):
    image = yield from get_image(img_url)
    save_image(image, img_url)


def thread_download_one(img_url):
    time.sleep(1)
    resp = requests.get(img_url)
    image = resp.text
    save_image(image, img_url)

if __name__ == '__main__':
    images_list = [
        'http://img3m0.ddimg.cn/67/4/24003310-1_b_5.jpg'
        'http://img3m2.ddimg.cn/43/13/23958142-1_b_12.jpg',
        'http://img3m0.ddimg.cn/60/17/24042210-1_b_5.jpg',
        'http://img3m4.ddimg.cn/20/11/23473514-1_b_5.jpg',
        'http://img3m4.ddimg.cn/40/14/22783504-1_b_1.jpg',
        'http://img3m7.ddimg.cn/43/25/23254747-1_b_3.jpg',
        'http://img3m9.ddimg.cn/30/36/23368089-1_b_2.jpg',
        'http://img3m1.ddimg.cn/77/14/23259731-1_b_0.jpg',
        'http://img3m2.ddimg.cn/33/18/23321562-1_b_21.jpg',
        'http://img3m3.ddimg.cn/2/21/22628333-1_b_2.jpg',
        'http://img3m8.ddimg.cn/85/30/23961748-1_b_10.jpg',
        'http://img3m1.ddimg.cn/90/34/22880871-1_b_3.jpg',
        'http://img3m2.ddimg.cn/62/27/23964002-1_b_6.jpg',
        'http://img3m5.ddimg.cn/84/16/24188655-1_b_3.jpg',
        'http://img3m6.ddimg.cn/46/1/24144166-1_b_23081.jpg',
        'http://img3m9.ddimg.cn/79/8/8766529-1_b_0.jpg']
    start = time.time()
    loop = asyncio.get_event_loop()
    to_do_tasks = [download_one(img) for img in images_list]

    res, _ = loop.run_until_complete(asyncio.wait(to_do_tasks))
    print(len(res))
    print('asyncio cost:' + str(time.time() - start))
    # ======================多線程版本===============================
    start = time.time()
    with ThreadPoolExecutor() as executor:
        res = [executor.submit(thread_download_one, i) for i in images_list]
    print(len(res))
    print('Thread cost:' + time.time() - start)
代碼解讀
  1. 增加了多線程的下載函數thread_download_one, 和asyncio的方式一樣在http請求的時候阻塞1s
  2. 承接我們上一章的問題, 上一章的問題主要就是在save_image()函數, save_image操作硬盤保存文件, 控制權交還給主循環, 此刻有很多子生成器都返回了數據等待主線程的處理, 會導致主線程阻塞, 我們模擬耗時操作硬盤(休眠0.5s), 最終耗時8.63s, 而多線程耗時6.63s左右, asyncio比多線程效率更低了, 線程池多個線程并發的寫硬盤, 而此刻asyncio需要主線程處理完一個任務的寫硬盤操作之后才能處理下一個任務, 所以效率會很低。

知道了問題所在, 下一步要做的是改寫寫硬盤的操作, 這個操作不能阻塞主線程, asyncio也為我們提供了這樣的api, run_in_executor(), 該函數內部維護的是ThreadPoolExecutor線程池, 使用多線程的方式實現異步操作。

只需要改一下download_one函數

@asyncio.coroutine
def download_one(img_url):
    image = yield from get_image(img_url)
    loop = asyncio.get_event_loop()
    loop.run_in_executor(None, save_image, image, img_url)

再次執行一下看一下運行時間。我執行1.3s, 相比于8.63s好了不少

補充:

  1. download_one函數中創建的loop循環對象和main函數中的loop對象是同一個, 可以看看源碼或者id()一下
  2. 主函數中不要loop.close(), run_in_executor函數每次都會調用self._check_closed()檢測循環是否關閉
    3.書本中還介紹了yield from semaphore來限制并發請求數量, 由于asyncio不向多線程那樣阻塞, 加入循環事件任務被快速驅動, 并發訪問人家的網頁, 所以使用semaphore來及限制并發的數量, 讓你的程序溫柔對待他人的網站。這一塊可以結合書中的代碼學習, 這里不展開
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Object C中創建線程的方法是什么?如果在主線程中執行代碼,方法是什么?如果想延時執行代碼、方法又是什么? 1...
    AlanGe閱讀 1,793評論 0 17
  • 注:本文很多素材來源于網絡上前人總結和《流暢的python》一書,本人僅僅以個人視角重新整合,便于自己理解,再此聲...
    第八共同體閱讀 5,648評論 0 4
  • 本文是17年寫的,至今過去多年,有一篇更好的文檔: https://superfastpython.com/pyt...
    人世間閱讀 104,829評論 51 234
  • 太愛一個人,我們就會變得多疑、猜忌、敏感;變態的惱怒、莫名的不安和焦慮,還有不著邊際的自我幻想。所以,如果太愛一個...
    我麋鹿啦閱讀 206評論 0 1
  • 朋友將我送至火車站,我們便分別了。我趕赴A城開報告會,在悶熱的夏季里搭乘火車,往返兩城之間趕會場,真不算是美差。握...
    正好周沫閱讀 286評論 2 0