program story

Python의 asyncio 모듈을 사용하여 동시 작업을 올바르게 생성하고 실행하는 방법은 무엇입니까?

inputbox 2020. 11. 21. 14:09
반응형

Python의 asyncio 모듈을 사용하여 동시 작업을 올바르게 생성하고 실행하는 방법은 무엇입니까?


TaskPython 3의 비교적 새로운 asyncio모듈을 사용하여 동시에 실행되는 두 개의 객체 를 올바르게 이해하고 구현하려고 합니다.

간단히 말해서, asyncio Task는 이벤트 루프를 통한 비동기 프로세스 및 동시 실행 을 처리하도록 설계된 것 같습니다 . await이벤트 루프를 차단하지 않고 결과를 기다렸다가 사용할 수있는 콜백없는 방법으로 (비동기 함수에 적용됨) 사용을 촉진합니다 . (미래와 콜백은 여전히 ​​실행 가능한 대안입니다.)

또한 코 루틴을 래핑하도록 설계된 asyncio.Task()의 특수 하위 클래스 인 클래스를 제공합니다 Future. asyncio.ensure_future()메서드 를 사용하여 호출하는 것이 좋습니다. asyncio 작업의 용도는 독립적으로 실행되는 작업이 동일한 이벤트 루프 내에서 다른 작업과 '동시에'실행되도록하는 것입니다. 내 이해는 Tasks이벤트 루프에 연결되어 await명령문 사이의 코 루틴을 자동으로 계속 구동한다는 것 입니다.

Executor클래스 중 하나를 사용할 필요없이 동시 태스크를 사용할 수 있다는 생각이 마음에 들지만 구현에 대한 자세한 내용을 찾지 못했습니다.

이것이 내가 현재하는 방법입니다.

import asyncio

print('running async test')

async def say_boo():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...boo {0}'.format(i))
        i += 1

async def say_baa():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...baa {0}'.format(i))
        i += 1

# wrap in Task object
# -> automatically attaches to event loop and executes
boo = asyncio.ensure_future(say_boo())
baa = asyncio.ensure_future(say_baa())

loop = asyncio.get_event_loop()
loop.run_forever()

두 개의 루핑 태스크를 동시에 실행하려는 경우 태스크에 내부 await표현식 이 없으면 while루프에 갇혀 다른 태스크가 실행되는 것을 효과적으로 차단합니다 (일반 while루프 와 매우 유사 함 ). 그러나 작업이 (a) 기다리면 문제없이 동시에 실행되는 것처럼 보입니다.

따라서이 await명령문은 작업간에 앞뒤로 전환 할 수있는 발판을 이벤트 루프에 제공하여 동시성의 효과를 제공하는 것 같습니다.

내부 출력 예 await:

running async test
...boo 0
...baa 0
...boo 1
...baa 1
...boo 2
...baa 2

내부가 없는 출력 예 await:

...boo 0
...boo 1
...boo 2
...boo 3
...boo 4

질문

이 구현은 동시 루핑 작업의 '적절한'예를 통과합니까 asyncio?

이것이 작동하는 유일한 방법 은 이벤트 루프가 여러 작업을 처리 Task할 수 있도록 차단 지점 ( await표현식) 을 제공하는 것 입니까?


예, 이벤트 루프 내에서 실행중인 코 루틴은 다른 코 루틴과 작업이 실행되지 않도록 차단합니다.

  1. yield from또는 await(Python 3.5 이상을 사용하는 경우)를 사용 하여 다른 코 루틴을 호출 합니다.
  2. 보고.

이는 asyncio단일 스레드 이기 때문 입니다. 이벤트 루프가 실행되는 유일한 방법은 다른 코 루틴이 능동적으로 실행되지 않는 것입니다. yield from/ 사용 await은 코 루틴을 일시적으로 중단하여 이벤트 루프가 작동 할 수있는 기회를 제공합니다.

Your example code is fine, but in many cases, you probably wouldn't want long-running code that isn't doing asynchronous I/O running inside the event loop to begin with. In those cases, it often makes more sense to use BaseEventLoop.run_in_executor to run the code in a background thread or process. ProcessPoolExecutor would be the better choice if your task is CPU-bound, ThreadPoolExecutor would be used if you need to do some I/O that isn't asyncio-friendly.

Your two loops, for example, are completely CPU-bound and don't share any state, so the best performance would come from using ProcessPoolExecutor to run each loop in parallel across CPUs:

import asyncio
from concurrent.futures import ProcessPoolExecutor

print('running async test')

def say_boo():
    i = 0
    while True:
        print('...boo {0}'.format(i))
        i += 1


def say_baa():
    i = 0
    while True:
        print('...baa {0}'.format(i))
        i += 1

if __name__ == "__main__":
    executor = ProcessPoolExecutor(2)
    loop = asyncio.get_event_loop()
    boo = asyncio.ensure_future(loop.run_in_executor(executor, say_boo))
    baa = asyncio.ensure_future(loop.run_in_executor(executor, say_baa))

    loop.run_forever()

You don't necessarily need a yield from x to give control over to the event loop.

In your example, I think the proper way would be to do a yield None or equivalently a simple yield, rather than a yield from asyncio.sleep(0.001):

import asyncio

@asyncio.coroutine
def say_boo():
  i = 0
  while True:
    yield None
    print("...boo {0}".format(i))
    i += 1

@asyncio.coroutine
def say_baa():
  i = 0
  while True:
    yield
    print("...baa {0}".format(i))
    i += 1

boo_task = asyncio.async(say_boo())
baa_task = asyncio.async(say_baa())

loop = asyncio.get_event_loop()
loop.run_forever()

Coroutines are just plain old Python generators. Internally, the asyncio event loop keeps a record of these generators and calls gen.send() on each of them one by one in a never ending loop. Whenever you yield, the call to gen.send() completes and the loop can move on. (I'm simplifying it; take a look around https://hg.python.org/cpython/file/3.4/Lib/asyncio/tasks.py#l265 for the actual code)

That said, I would still go the run_in_executor route if you need to do CPU intensive computation without sharing data.

참고URL : https://stackoverflow.com/questions/29269370/how-to-properly-create-and-run-concurrent-tasks-using-pythons-asyncio-module

반응형