Python說文解字_Python之多任務_05

問:在Py3.5以前yield表現很是好,在Py3.5以後爲了將予以變得更加明確,就引入了async和await關鍵詞用於定義原生的協議。html

答:async和await原生協程:nginx

async def downloader(url):
    return "bobby"

async def downloader_url(url):
    # do something
    html = await downloader(url)
    return html

if __name__ == '__main__':
    coro = downloader_url("http://www.baidu.com")
    next(None)
    # coro.send(None)

# 1.若是調用send正常
# StopIteration: bobby
# 2.若是調用next草異常,所以原生協程只能用send
# TypeError: 'NoneType' object is not an iterator
# sys:1: RuntimeWarning: coroutine 'downloader_url' was never awaited

  咱們發現:原生協程只能用send不能用next。並且發現 原生協程和yield協程差很少,前面加上了async語法,await相似於yield from。Python引入了async和await原生協程是爲了咱們的語義更加的清晰。若是咱們用生成器寫出的協程的話,代碼很是的凌亂的。由於它又能當生成器又能當協程,顯得比較凌亂,將這兩種區分開來。所以async裏面是不能定義yield的。所以Python增強了咱們的區別。所以這兩個是一對的。這樣咱們的協程區分開來。前面說了那麼多生成器就是爲了增強協程的理解。這樣咱們在協程裏面就用這兩個。所以在Python內部依然沿用了生成器的原理,來實現了咱們的協程。程序員

  await後跟隨的Awaitbale對象。咱們能夠經過from  collections import Awaitalbe模塊。web

  其實這個是實現了魔法拿書中的__await__的方法,所以咱們還可使用裝飾器的方法來操做,省去asyn,而變換成咱們熟悉的生成器的樣子。代碼以下:django

import types

@types.coroutine
def downloader(url):
    yield "bobby"

async def downloader_url(url):
    # do something
    html = await downloader(url)
    return html

if __name__ == '__main__':
    coro = downloader_url("http://www.baidu.com")
    # next(None)
    coro.send(None)

 

 

問:生成器是如何變成咱們協程的?編程

答:在開始我麼引入過協程的需求,咱們的協程是經過單線程調度,協程是咱們函數級別的是由咱們程序員本身來決定調用的,咱們能夠寫同步代碼同樣寫異步代碼。咱們的生成器就能夠完成咱們的協程的這麼一個功能。咱們如今就能夠用協程來模擬咱們的需求。flask

  生成器是能夠暫停的函數,實際上生成器是能夠有狀態的!服務器

  咱們看這段代碼多線程

import inspect
def gen_func():
    yield 1
    return "bobby"

if __name__ == '__main__':
    gen = gen_func()
    print(inspect.getgeneratorstate(gen)) # GEN_CREATED
    next(gen)
    print(inspect.getgeneratorstate(gen)) # GEN_SUSPENDED
    try:
        next(gen)
    except StopIteration:
        pass
    print(inspect.getgeneratorstate(gen)) # GEN_CLOSED

  經過inspect中的getgeneratorstate咱們來觀察生成器的狀態,實際上咱們在定義咱們的生成器的時候,生成器能夠接收咱們的值。這句話有兩個意思:第一是返回值給調用方,第二調用方經過send方式返回值跟gen。如今咱們生成器由「生產者」變爲「消費者」。
併發

  1.咱們用同步的方式編寫異步的代碼。

  2.在適當的時候暫停函數,並在適當的時候啓動函數。

  如今咱們模式:事件循環+協程模式

  咱們在函數當中的子函數,若是出現異常,會拋給這個函數的主函數,是「向上拋」的過程。這個就很好。協程是一個單線程模式

 

問:異步IO和IO複用,也就是同步IO和異步IO。

答:咱們對前面的東西略微作一個小結:

  異步IO和協程:如今咱們尚未把協程來用到咱們的編碼當中,協程是須要事件循環來實現的。單獨使用的話做用不是很明顯。

  在最開始的時候我麼說到了併發、並行、異步、同步、阻塞、非阻塞。

  在IO多路複用(同步IO)當中的select poll epoll,使咱們使用的最多的技術。回調+事件循環的方式。這種編程模式和同步IO的編程模式差異很大。

  所以這兩種模式:回調+事件循環(IO多路複用)、協程+事件循環(異步IO)

  上面的編碼是很是痛苦的:回調之痛。

  咱們引入了生成器和協程,協程並不會別上面的方式高,協程主要解決的問題是回調之痛的問題和編碼習慣的問題。

  咱們能夠將生成器編程咱們的協程了。

  最後引入了async和await來區別生成器和協程,不容易混亂,進行區分。咱們能夠用Cororoutine裝飾器的方式,就不要用了。

  因此建議使用async和await的方式。

 

問:async IO併發編程:

答:該模塊是在Python3.4後引入的模塊,這是Python編程中最難的部分。該模塊也是Python最具野心的模塊。分幾個部分開始講解:

  1. 事件循環:

  咱們能夠把async IO看作一個模塊也可看作一個框架,它完成了整套異步編程中最核心的內容。它包含各類特定系統實現的模塊化事件循環,傳輸和協議抽象;對TCP,UDP,SSL,子進程,延時調用以及其餘的具體支持;模仿futures模塊但適用於事件循環使用Future類;基於yield from的協議和任務,可讓你用順序的方式編寫併發代碼;必須使用一個將產生阻塞IO的調用時,有接口能夠把這個事件轉移到線程池。能夠將多進程和多線程協調進來。

  協程編碼模式都逃離不掉三個要素:事件循環+調用(驅動生成器)+epoll(IO多路複用)

  asyncio 是Python用於解決異步IO編程的一整套解決方案。

  tornado、gevent、twisted(scrapy,django channels)

  tornado:實現了web服務器,djago+flask是Python最傳統要搭配(uwsgi,gunicorn+nginx),tornado能夠直接部署,nginx+tornado

 

  使用asyncio

import asyncio # 能夠當作協程池來理解比較容易

import time


async def get_html(url):
    print("start get url")
    # time.sleep(2) # 阻塞式的IO不能寫在裏面
    await asyncio.sleep(2) # 不能使用import time,必需要加await
    print("end get url")

if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(get_html("htttp://www.baidu.com"))
    print(time.time() - start_time)

# start get url
# end get url
# 2.0150375366210938

 

   get_event_loop市價循環

  run_until_complete去執行

  這裏不能用time.sleep這是阻塞式的方法。所以會單獨的一個一個執行很是慢,因此要使用asynic中的sleep

import asyncio # 能夠當作協程池來理解比較容易

import time


async def get_html(url):
    print("start get url")
    # time.sleep(2) # 阻塞式的IO不能寫在裏面
    await asyncio.sleep(2) # 不能使用import time,必需要加await
    print("end get url")

if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    tasks = [get_html("htttp://www.baidu.com") for i in range(100)]
    loop.run_until_complete(asyncio.wait(tasks))
    print(time.time() - start_time)

# start get url
# end get url
# 2.0150375366210938
# time編程順序執行。asyncio.sleep()能夠當即執行。只要一個地方阻塞了其餘方面都實現不了。

  咱們發現更改後就會阻塞。

 

import asyncio # 能夠當作協程池來理解比較容易

import time


async def get_html(url):
    print("start get url")
    # time.sleep(2) # 阻塞式的IO不能寫在裏面
    await asyncio.sleep(2) # 不能使用import time,必需要加await
    return "bobby"
if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    # get_future = asyncio.ensure_future(get_html("htttp://www.baidu.com"))
    # loop.create_task()
    # tasks = [get_html("htttp://www.baidu.com") for i in range(100)]
    task = loop.create_task(get_html("htttp://www.baidu.com"))
    loop.run_until_complete(task)
    print(time.time() - start_time)
    print(task.result())


# 獲取協程的返回值

  咱們用協程調用線程池:ensure_funture

  使用方法還有create_task這兩種都是比較好理解的。

 

import asyncio # 能夠當作協程池來理解比較容易

import time
from functools import partial

async def get_html(url):
    print("start get url")
    # time.sleep(2) # 阻塞式的IO不能寫在裏面
    await asyncio.sleep(2) # 不能使用import time,必需要加await
    return "bobby"

def callback(url,future):
    print("send email to bobby")

if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    # get_future = asyncio.ensure_future(get_html("htttp://www.baidu.com"))
    # loop.create_task()
    # tasks = [get_html("htttp://www.baidu.com") for i in range(100)]
    task = loop.create_task(get_html("htttp://www.baidu.com"))
    # task.add_done_callback(callback)
    task.add_done_callback(partial(callback,"htttp://www.baidu.com"))
    loop.run_until_complete(task)
    print(time.time() - start_time)
    print(task.result())


# 獲取協程的返回值

 

  咱們也可使用回調,在task中的重寫add_done_callback方法。

 

import asyncio # 能夠當作協程池來理解比較容易

import time

async def get_html(url):
    print("start get url")
    # time.sleep(2) # 阻塞式的IO不能寫在裏面
    await asyncio.sleep(2) # 不能使用import time,必需要加await
    print("end get url")

if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    tasks = [get_html("htttp://www.baidu.com") for i in range(100)]
    # loop.run_until_complete(asyncio.wait(tasks))
    loop.run_until_complete(asyncio.gather(*tasks))
    print(time.time() - start_time)

# wait 和 gather 的區別
# gather更加高層,能夠將咱們task分組
    group1 = [get_html("htttp://www.baidu1.com") for i in range(100)]
    group2 = [get_html("htttp://www.baidu2.com") for i in range(100)]
    loop.run_until_complete(asyncio.gather(*group1,*group2))

    group1 = asyncio.gather(*group1)
    group2 = asyncio.gather(*group2)

    group2.cancel()

 

  咱們儘可能使用gather方法,注意他是能夠將咱們task進行分組,後面要加上*參數的形式。

 

  2. task取消、嵌套、字寫成調用原理

# import asyncio
#
# loop = asyncio.get_event_loop()
# loop.run_forever()
# loop.run_until_complete()
# 1.loop會被放到future中。
# 2.取消future(task)

 

import asyncio
import time

async def get_html(sleep_times):
    print("waiting")
    await asyncio.sleep(sleep_times)
    print("done after {}s".format(sleep_times))

if __name__ == '__main__':
    task1 = get_html(2)
    task2 = get_html(3)
    task3 = get_html(2)
    tasks = [task1,task2,task3]

    loop = asyncio.get_event_loop()

    try:
        loop.run_until_complete(asyncio.wait(tasks))
    except KeyboardInterrupt as e:
        all_task = asyncio.Task.all_tasks()
        for task in all_task:
            print("cancel task")
            task.cancel()
        loop.stop()
        loop.run_forever()
    finally:
        loop.close()

 

  3. call_soon() 即刻執行,call_later(),call_at()

 

  4.ThreadPoolExector   + asyncio

  使用多線程:在協程中繼承阻塞io

  生成器 = ThreadPoolExecutor

  run_in+executor(生成器,函數,參數)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

11111

相關文章
相關標籤/搜索