asyncio를 사용하여 Python에서 비동기식 프로그래밍

23265 단어 pythonasyncio
자바스크립트에서 온 사람들에게 비동기 프로그래밍은 새로운 일이 아니지만,python 개발자에게 비동기 함수와 미래(JS의promise에 해당)에 익숙해지는 것은 결코 간단하지 않을 것이다

병발과 병행


병발성과 병렬성은 매우 비슷하지만 프로그래밍에서 중요한 차이가 있다.
요리를 하는 동시에 책을 한 권 쓴다. 두 가지 임무를 동시에 하는 것처럼 보여도, 두 가지 임무 사이를 전환하는 것이다. 물이 끓기를 기다릴 때, 책을 쓰지만, 채소를 썰 때, 글을 멈추는 것이다.이것은 병발이라고 부른다.이 두 가지 임무를 병행하여 완성하는 유일한 방법은 두 사람이 하나씩 쓰고 하나씩 끓이는 것이다. 이것이 바로 다핵 CPU가 하는 것이다.

왜 asyncio를 선택했는지


비동기 프로그래밍은 단일 라인에서 실행되는 병렬 코드를 작성할 수 있습니다.다중 스레드에 비해 첫 번째 장점은 스케줄러가 한 작업에서 다른 작업으로 전환하는 위치를 결정할 수 있다는 것이다. 이것은 작업 간에 데이터를 공유하는 것이 더욱 안전하고 쉽다는 것을 의미한다.
def queue_push_back(x):
    if len(list) < max_size:
        list.append(x)
만약 우리가 다중 루틴 프로그램에서 상술한 코드를 실행한다면, 두 루틴은 두 번째 줄을 동시에 실행할 수 있기 때문에, 두 항목은 동시에 대기열에 추가되고, 대기열의 크기를 크게 할 수 있다max_size비동기 프로그래밍의 또 다른 장점은 메모리 사용이다.새 루틴을 만들 때마다 상하문 전환을 허용하는 메모리를 사용합니다. 만약 우리가 비동기 프로그래밍을 사용한다면, 이것은 문제가 아닙니다. 코드가 한 루틴에서 실행되기 때문입니다.

어떻게python으로 비동기 코드를 작성합니까


Asyncio에는 세 가지 주요 구성 요소가 있습니다: 협정, 이벤트 순환 및 미래

협동 절차


협정은 비동기 함수의 결과로 async 이전의 키워드def를 사용하여 설명할 수 있다
async def my_task(args):
    pass

my_coroutine = my_task(args)
우리가 async 키워드 성명 함수를 사용할 때, 함수는 실행되지 않고 협의 대상으로 되돌아옵니다.
두 가지 방법으로 협정에서 비동기 함수의 출력을 읽을 수 있다.
첫 번째 방법은 await 키워드를 사용하는 것이다. 이것은 비동기 함수 내에서만 이루어질 수 있고 협정이 종료되고 결과가 되돌아오기를 기다리는 것이다
result = await my_task(args)
두 번째 방법은 이벤트 순환에 추가하는 것입니다. 다음 절에서 볼 수 있습니다.

이벤트 순환


이벤트 순환은 비동기 코드를 실행하고 비동기 함수 사이를 전환하는 방법을 결정하는 대상이다.이벤트 순환을 만들면 여러 개의 프로토콜을 추가할 수 있습니다. 호출 run_until_complete 이나 run_forever 이 프로토콜은 동시에 실행됩니다.
# create loop
loop = asyncio.new_event_loop()
# add coroutine to the loop
future = loop.create_task(my_coroutine)
# stop the program and execute all coroutine added
# to the loop concurrently
loop.run_until_complete(future)
loop.close()

장래


future는 비동기 함수 출력의 차지 문자로 사용되는 대상으로 함수 상태에 대한 정보를 제공합니다.
이벤트 순환에corutine를 추가하면 미래가 만들어집니다.두 가지 방법이 있습니다.
future1 = loop.create_task(my_coroutine)
# or
future2 = asyncio.ensure_future(my_coroutine)
첫 번째 방법은 순환에 협정을 추가하고 task 을 되돌려줍니다. 이것은future의 하위 형식입니다.두 번째 방법은 매우 비슷하다. 협정을 받아들이고 기본 순환에 추가하는 것이다. 유일한 차이점은 미래를 받아들일 수 있다는 것이다. 이런 상황에서 아무것도 하지 않고 변하지 않는 미래로 돌아가는 것이다.

간단한 프로그램


import asyncio

async def my_task(args):
    pass

def main():
    loop = asyncio.new_event_loop()
    coroutine1 = my_task()
    coroutine2 = my_task()
    task1 = loop.create_task(coroutine1)
    task2 = loop.create_task(coroutine2)
    loop.run_until_complete(asyncio.wait([task1, task2]))
    print('task1 result:', task1.result())
    print('task2 result:', task2.result())
    loop.close()
보시다시피 비동기 함수를 실행하려면 우선 협정을 만들고 미래/작업을 만드는 이벤트 순환에 추가해야 합니다.지금까지, 우리가 loop.run_until_completed 이벤트 순환을 호출하여 loop.create_task 또는 asyncio.ensure_future 순환에 추가된 모든 협정을 실행할 때만 비동기 함수에 있는 코드가 실행되지 않았습니다.loop.run_until_completed 당신이 제시한 미래가 논점으로 끝날 때까지 당신의 절차를 막을 것이다.이 예에서 우리는 asyncio.wait() 를 사용하여 매개 변수 목록에서 전달된 모든 미래가 완성될 때만 완성되는 미래를 만들었다.

비동기 함수


python에서 비동기 함수를 작성할 때 기억해야 할 점은 async 이전에 def 을 사용했기 때문에 함수가 동시에 실행된다는 것을 의미하지 않는다는 것입니다.일반 함수를 가져오고 그 앞에 async 을 추가하면 이벤트 순환은 함수를 중단할 수 있는 위치를 지정하지 않았기 때문에 함수를 중단할 수 있습니다.이벤트 순환이 협동 루트를 변경할 수 있는 위치를 지정하는 것은 매우 간단합니다. 키워드wait를 사용할 때마다 이벤트 순환은 함수 운행을 멈추고 이 순환에 등록된 다른 협동 루트를 실행할 수 있습니다.
async def print_numbers_async1(n, prefix):
    for i in range(n):
        print(prefix, i)

async def print_numbers_async2(n, prefix):
    for i in range(n):
        print(prefix, i)
        if i % 5 == 0:
            await asyncio.sleep(0)

loop1 = asyncio.new_event_loop()
count1_1 = loop1.create_task(print_numbers_async1(10, 'c1_1')
count2_1 = loop1.create_task(print_numbers_async1(10, 'c2_1')
loop1.run_until_complete(asyncio.wait([count1_1, count2_1])
loop1.close()

loop2 = asyncio.new_event_loop()
count1_2 = loop1.create_task(print_numbers_async1(10, 'c1_2')
count2_2 = loop1.create_task(print_numbers_async1(10, 'c2_2')
loop2.run_until_complete(asyncio.wait([count1_2, count2_2])
loop2.close()
이 코드를 실행하면 loop1이 먼저 접두사 c1_1 를 가진 모든 숫자를 인쇄한 다음에 접두사 c2_1 를 가진 모든 숫자를 인쇄하고 두 번째 순환에서 다섯 개의 숫자마다 순환이 작업을 변경합니다.

실제 세계의 예


현재 우리는python 비동기 프로그래밍의 기초 지식을 이해했고, 인터넷에서 페이지 목록을 다운로드하고, 페이지 앞의 세 줄을 포함하는 미리보기를 인쇄할 수 있는 더 현실적인 코드를 작성했다.
import aiohttp
import asyncio

async def print_preview(url):
    # connect to the server
    async with aiohttp.ClientSession() as session:
        # create get request
        async with session.get(url) as response:
            # wait for response
            response = await response.text()

            # print first 3 not empty lines
            count = 0
            lines = list(filter(lambda x: len(x) > 0, response.split('\n')))
            print('-'*80)
            for line in lines[:3]:
                print(line)
            print()

def print_all_pages():
    pages = [
        'http://textfiles.com/adventure/amforever.txt',
        'http://textfiles.com/adventure/ballyhoo.txt',
        'http://textfiles.com/adventure/bardstale.txt',
    ]

    tasks =  []
    loop = asyncio.new_event_loop()
    for page in pages:
        tasks.append(loop.create_task(print_preview(page)))

    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
이 코드는 이해하기 쉬울 것이다. 우리는 먼저 비동기 함수를 만들어서 URL을 다운로드하고 앞의 세 줄의 비공행을 인쇄한다.그리고 이 함수는 페이지 목록 호출 print_preview 의 모든 페이지를 순환에 추가하고 작업 목록에 저장하는 함수를 만듭니다.마지막으로, 이벤트 순환을 실행합니다. 이벤트 순환은 우리가 추가한 협정을 실행하고, 모든 페이지의 미리보기를 출력합니다.

비동기 발전기


내가 말하고 싶은 마지막 특성은 비동기 발전기다.비동기 생성기를 실현하는 것은 매우 간단하다.
import asyncio
import math
import random

async def is_prime(n):
    if n < 2:
        return True
    for i in range(2, n):
        # allow event_loop to run other coroutine
        await asyncio.sleep(0)
        if n % i == 0:
            return False
    return True

async def prime_generator(n_prime):
    counter = 0
    n = 0
    while counter < n_prime:
        n += 1
        # wait for is_prime to finish
        prime = await is_prime(n)
        if prime:
            yield n
            counter += 1

async def check_email(limit):
    for i in range(limit):
        if random.random() > 0.8:
            print('1 new email')
        else:
            print('0 new email')
        await asyncio.sleep(2)

async def print_prime(n):
    async for prime in prime_generator(n):
        print('new prime number found:', prime)

def main():
    loop = asyncio.new_event_loop()
    prime = loop.create_task(print_prime(3000))
    email = loop.create_task(check_email(10))
    loop.run_until_complete(asyncio.wait([prime, email]))
    loop.close()

이상 처리


프로그래밍에서 처리되지 않은 이상을 일으킬 때, 정상적인 동기화 프로그래밍에서처럼 프로그램을 중단하지 않고, 반대로 미래에 저장되며, 프로그램이 종료되기 전에 이 이상을 처리하지 않으면 다음과 같은 오류가 발생할 수 있습니다.
Task exception was never retrieved
이 문제를 해결할 수 있는 두 가지 방법이 있다. 미래 결과를 방문할 때 이상을 포착하거나 미래 이상을 호출하는 것이다.
try:
    # this will raise the exception raised during the coroutine execution
    my_promise.result()
catch Exception:
    pass

# this will return the exception raised during the coroutine execution
my_promise.exception()

깊이 연구하다


만약 지금까지 모든 내용을 읽었다면, asyncio를 사용하여 코드를 작성하고 보내는 방법을 알아야 하지만, asyncio의 작업 원리를 깊이 이해하고 싶다면, 아래의 동영상을 보시는 것을 권장합니다.
더 복잡한 asyncio 사용을 보고 싶거나 문제가 있으면 댓글을 남겨주세요. 최대한 빨리 재방송해 드릴게요.

좋은 웹페이지 즐겨찾기