asyncio는 실제로 어떻게 작동합니까?


120

이 질문은 내 다른 질문에 의해 동기가 부여되었습니다. cdef에서 기다리는 방법?

에 대한 많은 기사와 블로그 게시물이 웹에 asyncio있지만 모두 매우 피상적입니다. asyncio실제로 구현되는 방법 과 I / O를 비동기로 만드는 이유 에 대한 정보를 찾을 수 없습니다 . 소스 코드를 읽으려고했지만 최고 등급의 C 코드가 아닌 수천 줄의 코드로 많은 부분이 보조 객체를 다루고 있지만 가장 중요한 것은 Python 구문과 번역 할 C 코드를 연결하기가 어렵다는 것입니다. 으로.

Asycnio의 자체 문서는 훨씬 덜 유용합니다. 작동 방식에 대한 정보는 없으며 사용 방법에 대한 지침 만 있으며 때로는 오해의 소지가 있거나 매우 잘못 작성되었습니다.

저는 Go의 코 루틴 구현에 익숙하며 Python이 동일한 작업을 수행하기를 바라고 있습니다. 이 경우 위에 링크 된 게시물에서 작성한 코드가 작동했을 것입니다. 그렇지 않았기 때문에 이제 이유를 알아 내려고 노력하고 있습니다. 지금까지 내 가장 좋은 추측은 다음과 같습니다. 내가 틀린 부분을 수정하십시오.

  1. 형식의 프로 시저 정의 async def foo(): ...는 실제로를 상속하는 클래스의 메서드로 해석됩니다 coroutine.
  2. 아마도 async def실제로는 await문에 의해 여러 메서드로 분할되며 , 여기서 이러한 메서드가 호출 된 객체는 지금까지 실행을 통해 수행 한 진행 상황을 추적 할 수 있습니다.
  3. 위의 내용이 사실이라면, 본질적으로 코 루틴의 실행은 일부 글로벌 관리자 (루프?)가 코 루틴 객체의 메서드를 호출하는 것으로 귀결됩니다.
  4. 전역 관리자는 I / O 작업이 Python (오직?) 코드에 의해 수행되는시기를 어떻게 든 (어떻게?) 인식하고 현재 실행중인 메서드가 제어를 포기한 후 실행할 보류중인 코 루틴 메서드 중 하나를 선택할 수 있습니다 ( await문에 적중) . ).

다른 말로하면, asyncio좀 더 이해할 수있는 것으로 일부 구문을 "desugaring"하려는 시도가 있습니다 .

async def coro(name):
    print('before', name)
    await asyncio.sleep()
    print('after', name)

asyncio.gather(coro('first'), coro('second'))

# translated from async def coro(name)
class Coro(coroutine):
    def before(self, name):
        print('before', name)

    def after(self, name):
        print('after', name)

    def __init__(self, name):
        self.name = name
        self.parts = self.before, self.after
        self.pos = 0

    def __call__():
        self.parts[self.pos](self.name)
        self.pos += 1

    def done(self):
        return self.pos == len(self.parts)


# translated from asyncio.gather()
class AsyncIOManager:

    def gather(*coros):
        while not every(c.done() for c in coros):
            coro = random.choice(coros)
            coro()

내 추측이 맞다면 문제가 있습니다. 이 시나리오에서 I / O는 실제로 어떻게 발생합니까? 별도의 스레드에서? 전체 통역사가 정지되고 I / O가 통역사 외부에서 발생합니까? I / O는 정확히 무엇을 의미합니까? 내 파이썬 프로 시저가 C open()프로 시저를 호출 하고 커널에 인터럽트를 전송하여 제어를 포기한다면, 파이썬 인터프리터는이를 어떻게 알고 다른 코드를 계속 실행할 수 있는지, 커널 코드는 실제 I / O를 수행하고 원래 인터럽트를 보낸 Python 절차를 깨 웁니다. 원칙적으로 파이썬 인터프리터는 이런 일이 일어나는 것을 어떻게 알 수 있습니까?


2
대부분의 로직은 이벤트 루프 구현에 의해 처리됩니다. CPython BaseEventLoop이 어떻게 구현 되는지 살펴보세요 : github.com/python/cpython/blob/…
Blender

@Blender 좋아, 마침내 내가 원하는 것을 찾았다 고 생각하지만 이제 코드가 원래대로 작성된 이유를 이해할 수 없습니다. _run_once이 전체 모듈에서 "비공개"로 만들어진 유일한 유용한 기능인 이유는 무엇 입니까? 구현은 끔찍하지만 문제는 적습니다. 이벤트 루프에서 호출하려는 유일한 함수가 "나를 호출하지 마십시오"로 표시되는 이유는 무엇입니까?
wvxvw

그것은 메일 링리스트에 대한 질문입니다. _run_once처음 에 만져야하는 사용 사례는 무엇입니까 ?
블렌더

8
그래도 내 질문에 대한 답은 아닙니다. 그냥 사용하여 유용한 문제를 어떻게 해결할 수 _run_once있습니까? asyncio복잡하고 결점이 있지만, 토론을 예의 바르게 유지하십시오. 자신이 이해하지 못하는 코드 배후의 개발자를 괴롭히지 마십시오.
Blender

1
@ user8371915 제가 다루지 않은 내용이 있다고 생각되면 제 답변을 추가하거나 의견을 남겨 주셔도 좋습니다.
Bharel

답변:


203

asyncio는 어떻게 작동합니까?

이 질문에 답하기 전에 몇 가지 기본 용어를 이해해야합니다. 이미 알고있는 용어가 있으면 생략하십시오.

발전기

생성기는 파이썬 함수의 실행을 중단 할 수있는 객체입니다. 사용자가 선별 한 생성기는 키워드를 사용하여 구현됩니다 yield. yield키워드를 포함하는 일반 함수를 생성하여 해당 함수를 생성기로 전환합니다.

>>> def test():
...     yield 1
...     yield 2
...
>>> gen = test()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

보시다시피 next()제너레이터를 호출 하면 인터프리터가 테스트 프레임을로드하고 yielded 값을 반환합니다 . next()다시 호출 하면 프레임이 인터프리터 스택에 다시로드되도록하고 yield다른 값을 계속 입력합니다.

세 번째 next()로 호출되면 발전기가 종료되고 StopIteration던져졌습니다.

발전기와 통신

: 발전기의 덜 알려진 기능은, 당신이 그 (것)들을 두 가지 방법을 사용하여 통신 할 수 있다는 사실이다 send()throw().

>>> def test():
...     val = yield 1
...     print(val)
...     yield 2
...     yield 3
...
>>> gen = test()
>>> next(gen)
1
>>> gen.send("abc")
abc
2
>>> gen.throw(Exception())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
Exception

를 호출 gen.send()하면 값이 yield키워드 의 반환 값으로 전달됩니다 .

gen.throw()다른 한편으로, 생성기 내부에서 예외를 던지는 것을 허용하고 같은 지점에서 발생한 예외 yield가 호출되었습니다.

생성기에서 값 반환

생성기에서 값을 반환하면 값이 StopIteration예외에 포함됩니다. 나중에 예외에서 값을 복구하여 필요에 따라 사용할 수 있습니다.

>>> def test():
...     yield 1
...     return "abc"
...
>>> gen = test()
>>> next(gen)
1
>>> try:
...     next(gen)
... except StopIteration as exc:
...     print(exc.value)
...
abc

새로운 키워드 : yield from

Python 3.4에는 새로운 키워드가 추가되었습니다 yield from. 이 키워드로 우리가 할 수있는 것은 모든 next(), send()그리고 throw()가장 안쪽의 중첩 생성기로 전달하는 것입니다. 내부 생성기가 값을 반환하면 다음의 반환 값이기도합니다 yield from.

>>> def inner():
...     inner_result = yield 2
...     print('inner', inner_result)
...     return 3
...
>>> def outer():
...     yield 1
...     val = yield from inner()
...     print('outer', val)
...     yield 4
...
>>> gen = outer()
>>> next(gen)
1
>>> next(gen) # Goes inside inner() automatically
2
>>> gen.send("abc")
inner abc
outer 3
4

이 주제에 대해 더 자세히 설명하기 위해 기사 를 작성 했습니다 .

함께 모아서

yield fromPython 3.4에서 새로운 키워드를 도입했을 때 , 우리는 이제 터널처럼 생성기 내부에서 생성기를 만들 수있었습니다.이 생성기는 가장 안쪽에서 가장 바깥 쪽 생성기로 데이터를 앞뒤로 전달합니다. 이것은 제너레이터 ( 코 루틴)에 대한 새로운 의미를 낳았습니다 .

코 루틴 은 실행 중에 중지 및 재개 할 수있는 함수입니다. Python에서는 async def키워드를 사용하여 정의됩니다 . 많은 발전기처럼, 그들도 자신의 고유 한 형태로 사용 yield from되는을 await. 이전 async과는 await파이썬 3.5에 도입 된, 우리는 (함께 생성 된 동일한 방식으로 발전기에서 코 루틴을 생성 yield from하는 대신 await).

async def inner():
    return 1

async def outer():
    await inner()

__iter__()메서드 를 구현하는 모든 반복기 또는 생성기와 마찬가지로 코 루틴 은 호출 __await__()될 때마다 계속할 수 있도록 구현 합니다 await coro.

확인해야 할 Python 문서 내에 멋진 시퀀스 다이어그램 이 있습니다.

asyncio에서는 코 루틴 함수와 별도로 작업미래라는 두 가지 중요한 개체가 있습니다 .

선물

Future는 __await__()메소드가 구현 된 객체 이며, 그 역할은 특정 상태와 결과를 유지하는 것입니다. 상태는 다음 중 하나 일 수 있습니다.

  1. PENDING-future에 결과 또는 예외 세트가 없습니다.
  2. CANCELED-미래가 다음을 사용하여 취소되었습니다. fut.cancel()
  3. FINISHED-다음을 사용하는 결과 세트 fut.set_result()또는 다음을 사용 하는 예외 세트에 의해 미래가 완료되었습니다.fut.set_exception()

여러분이 짐작 한 것처럼 결과는 반환 될 Python 객체이거나 발생할 수있는 예외 일 수 있습니다.

객체의 또 다른 중요한 기능은 future라는 메서드가 포함되어 있다는 것 add_done_callback()입니다. 이 메서드를 사용하면 예외가 발생 했든 완료 되었든 작업이 완료되는 즉시 함수를 호출 할 수 있습니다.

과제

태스크 객체는 코 루틴을 감싸고 가장 안쪽 및 가장 바깥 쪽 코 루틴과 통신하는 특별한 퓨처입니다. 코 루틴이 퓨처 일 때마다 퓨처 await는 작업으로 다시 전달되고 (에서와 같이 yield from) 작업이이를 수신합니다.

다음으로 작업은 자신을 미래에 연결합니다. add_done_callback()미래 를 부름으로써 그렇게합니다 . 지금부터 취소되거나 예외를 전달하거나 결과적으로 Python 객체를 전달하여 미래가 완료되면 작업의 콜백이 호출되고 다시 존재하게됩니다.

Asyncio

우리가 답해야 할 마지막 질문은 IO가 어떻게 구현됩니까?

asyncio 내부에는 이벤트 루프가 있습니다. 작업의 이벤트 루프. 이벤트 루프의 역할은 작업이 준비 될 때마다 작업을 호출하고 모든 노력을 단일 작업 시스템으로 조정하는 것입니다.

이벤트 루프의 IO 부분은라는 단일 중요한 함수를 기반으로합니다 select. Select는 아래의 운영 체제에 의해 구현되는 차단 기능으로, 소켓에서 들어 오거나 나가는 데이터를 대기 할 수 있습니다. 데이터가 수신되면 깨어나서 데이터를 수신 한 소켓 또는 쓰기 준비가 된 소켓을 반환합니다.

asyncio를 통해 소켓을 통해 데이터를 받거나 보내려고 할 때 실제로 아래에서 일어나는 일은 소켓에 즉시 읽거나 보낼 수있는 데이터가 있는지 먼저 확인하는 것입니다. 의 경우 .send()버퍼가 가득, 또는 .recv()버퍼가 비어있는 소켓은에 등록 select(단순히 목록 중 하나에 추가하여 기능 rlist에 대한 recvwlist대한 send) 및 해당 기능 awaitSA는 새로 만든 future그 소켓에 연결, 오브젝트.

사용 가능한 모든 작업이 퓨처를 기다리고있을 때 이벤트 루프는 호출 select하고 기다립니다. 소켓 중 하나에 들어오는 데이터가 있거나 send버퍼가 고갈되면 asyncio는 해당 소켓에 연결된 미래 객체를 확인하고 완료로 설정합니다.

이제 모든 마법이 일어납니다. 미래는 완료로 설정되고 이전에 추가 된 작업이 add_done_callback()다시 살아 .send()나고 가장 안쪽의 코 루틴 ( await체인 때문에)을 다시 시작하는 코 루틴을 호출 하고 근처 버퍼에서 새로 수신 된 데이터를 읽습니다. 흘렸다.

다음과 같은 경우 메서드 체인 다시 recv():

  1. select.select 기다립니다.
  2. 데이터가있는 준비 소켓이 리턴됩니다.
  3. 소켓의 데이터는 버퍼로 이동됩니다.
  4. future.set_result() 호출됩니다.
  5. 추가 된 작업 add_done_callback()이 이제 깨어납니다.
  6. 태스크는 .send()가장 안쪽의 코 루틴으로가는 코 루틴을 호출 하고이를 깨 웁니다.
  7. 데이터는 버퍼에서 읽고 겸손한 사용자에게 반환됩니다.

요약하면 asyncio는 기능을 일시 중지하고 다시 시작할 수있는 생성기 기능을 사용합니다. yield from가장 안쪽의 생성기에서 바깥쪽으로 데이터를 앞뒤로 전달할 수있는 기능을 사용 합니다. IO가 완료되기를 기다리는 동안 (OS select기능 을 사용하여 ) 기능 실행을 중지하기 위해이 모든 것을 사용합니다 .

그리고 무엇보다도? 한 기능이 일시 중지 된 동안 다른 기능이 실행되고 섬세한 패브릭 (asyncio)과 인터리브 될 수 있습니다.


12
설명이 더 필요하면 주저하지 말고 의견을 말하십시오. Btw, 나는 이것을 블로그 기사 또는 stackoverflow의 답변으로 작성해야하는지 완전히 확신하지 못합니다. 질문은 대답하기가 길다.
Bharel

1
비동기 소켓에서 데이터를 보내거나 받으려고하면 먼저 OS 버퍼를 확인합니다. 수신을 시도하고 버퍼에 데이터가없는 경우 기본 수신 함수는 Python에서 예외로 전파되는 오류 값을 반환합니다. 전송 및 전체 버퍼와 동일합니다. 예외가 발생하면 Python은 해당 소켓을 프로세스를 일시 중단하는 select 함수로 차례로 보냅니다. 그러나 그것은 asyncio가 작동하는 방식이 아니라 선택 및 소켓이 작동하는 방식이며 이는 매우 OS에 따라 다릅니다.
Bharel

2
@ user8371915 항상 도움이 필요합니다. :-) Asyncio를 이해 하려면 생성기, 생성기 통신 및 yield from작동 방식을 알아야 합니다. 그러나 독자가 이미 그것에 대해 알고있는 경우 건너 뛸 수 있다는 점에 주목했습니다. :-) 내가 추가해야한다고 생각하는 다른 것이 있습니까?
Bharel

2
Asyncio 섹션 이전의 것들은 아마도 언어가 실제로 스스로하는 유일한 일이기 때문에 아마도 가장 중요 할 것입니다. 이는 select비 차단 I / O 시스템 호출이 OS에서 작동하는 방식이기 때문에 자격 이 될 수 있습니다. 실제 asyncio구조와 이벤트 루프는 이러한 것들로 만든 앱 수준 코드 일뿐입니다.
MisterMiyagi

3
이 게시물에는 Python에서 비동기 I / O의 백본에 대한 정보가 있습니다. 그런 친절한 설명에 감사드립니다.
mjkim

83

에 대해 이야기 async/await하고하는 것은 asyncio같은 일이 아닙니다. 첫 번째는 기본적인 저수준 구조 (코 루틴)이고, 후자는 이러한 구조를 사용하는 라이브러리입니다. 반대로, 궁극적 인 답은 하나도 없습니다.

다음은 방법에 대한 일반적인 설명입니다 async/awaitasyncio-like 라이브러리 작동합니다. 즉, 위에 다른 트릭이있을 수 있지만 (...) 직접 구축하지 않는 한 중요하지 않습니다. 그러한 질문을 할 필요가 없을만큼 이미 충분히 알고 있지 않는 한 그 차이는 무시할 만합니다.

1. 너트 쉘의 코 루틴 대 서브 루틴

서브 루틴 (함수, 프로 시저, ...) 과 마찬가지로 코 루틴 (생성자, ...)은 호출 스택과 명령어 포인터의 추상화입니다. 실행중인 코드 조각 스택이 있으며 각각은 특정 명령어에 있습니다.

의 구별 def대는 async def명확성을 위해 단지이다. 실제 차이는 returnyield. 이것으로부터 await또는 yield from전체 스택에 대한 개별 호출의 차이를 가져옵니다.

1.1. 서브 루틴

서브 루틴은 지역 변수를 보유하는 새로운 스택 레벨과 끝에 도달하기위한 명령어의 단일 순회를 나타냅니다. 다음과 같은 서브 루틴을 고려하십시오.

def subfoo(bar):
     qux = 3
     return qux * bar

실행하면

  1. bar및에 대한 스택 공간 할당qux
  2. 재귀 적으로 첫 번째 명령문을 실행하고 다음 명령문으로 이동
  3. 한 번에 return값을 호출 스택으로 푸시합니다.
  4. 스택 (1.) 및 명령 포인터 (2.)를 지 웁니다.

특히 4.는 서브 루틴이 항상 동일한 상태에서 시작 함을 의미합니다. 함수 자체에 대한 모든 것은 완료시 손실됩니다. 이후에 지침이 있어도 기능을 재개 할 수 없습니다 return.

root -\
  :    \- subfoo --\
  :/--<---return --/
  |
  V

1.2. 영구 서브 루틴으로서의 코 루틴

코 루틴은 서브 루틴과 비슷하지만 상태 파괴 하지 않고 종료 할 수 있습니다 . 다음과 같은 코 루틴을 고려하십시오.

 def cofoo(bar):
      qux = yield bar  # yield marks a break point
      return qux

실행하면

  1. bar및에 대한 스택 공간 할당qux
  2. 재귀 적으로 첫 번째 명령문을 실행하고 다음 명령문으로 이동
    1. 한 번에 yield해당 값을 호출 스택에 푸시 하지만 스택 및 명령어 포인터를 저장합니다.
    2. 를 호출하면 yield스택 및 명령어 포인터를 복원하고 인수를qux
  3. 한 번에 return값을 호출 스택으로 푸시합니다.
  4. 스택 (1.) 및 명령 포인터 (2.)를 지 웁니다.

2.1과 2.2가 추가되었습니다. 코 루틴은 미리 정의 된 지점에서 일시 중단 및 재개 될 수 있습니다. 이것은 다른 서브 루틴을 호출하는 동안 서브 루틴이 일시 중단되는 방법과 유사합니다. 차이점은 활성 코 루틴이 호출 스택에 엄격하게 바인딩되지 않는다는 것입니다. 대신, 일시 중단 된 코 루틴은 별도의 격리 된 스택의 일부입니다.

root -\
  :    \- cofoo --\
  :/--<+--yield --/
  |    :
  V    :

즉, 중단 된 코 루틴을 스택간에 자유롭게 저장하거나 이동할 수 있습니다. 코 루틴에 액세스 할 수있는 모든 호출 스택은 코 루틴을 재개하도록 결정할 수 있습니다.

1.3. 호출 스택 탐색

지금까지 코 루틴은 yield. 서브 루틴은 아래로 갈 수있는 최대 와 호출 스택 return(). 완전성을 위해 코 루틴은 호출 스택을 올라가는 메커니즘도 필요합니다. 다음과 같은 코 루틴을 고려하십시오.

def wrap():
    yield 'before'
    yield from cofoo()
    yield 'after'

실행하면 서브 루틴처럼 스택 및 명령어 포인터를 여전히 할당한다는 의미입니다. 일시 중단되면 여전히 서브 루틴을 저장하는 것과 같습니다.

그러나 yield from않습니다 모두 . 스택 및 명령 포인터를 일시 중단 wrap 하고 실행 cofoo합니다. 이 참고 wrap될 때까지 중지 상태로 남아 있습니다 cofoo완전히 마감. cofoo일시 중단되거나 무언가가 전송 될 때마다 cofoo호출 스택에 직접 연결됩니다.

1.4. 끝까지 코 루틴

설정된대로 yield from다른 중간 범위에 걸쳐 두 범위를 연결할 수 있습니다. 재귀 적으로 적용 하면 스택 맨 위 가 스택 맨 아래 에 연결될 수 있습니다 .

root -\
  :    \-> coro_a -yield-from-> coro_b --\
  :/ <-+------------------------yield ---/
  |    :
  :\ --+-- coro_a.send----------yield ---\
  :                             coro_b <-/

rootcoro_b서로에 대해 알고하지 않습니다. 이것은 코 루틴을 콜백보다 훨씬 더 깨끗하게 만듭니다. 코 루틴은 여전히 ​​서브 루틴과 같은 1 : 1 관계에 구축됩니다. 코 루틴은 일반 호출 지점까지 전체 기존 실행 스택을 일시 중지하고 다시 시작합니다.

특히 root재개 할 코 루틴을 임의의 수로 가질 수 있습니다. 그러나 동시에 둘 이상을 재개 할 수는 없습니다. 동일한 루트의 코 루틴은 동시이지만 병렬이 아닙니다!

1.5. 파이썬 asyncawait

설명은 지금까지 명시 적으로 사용하고있다 yieldyield from발전기의 어휘 - 기본 기능은 동일합니다. 새로운 Python3.5 구문 asyncawait주로 명확성을 위해 존재한다.

def foo():  # subroutine?
     return None

def foo():  # coroutine?
     yield from foofoo()  # generator? coroutine?

async def foo():  # coroutine!
     await foofoo()  # coroutine!
     return None

async forasync with당신이 휴식 때문에 문이 필요하다 yield from/await베어 체인 forwith문을.

2. 간단한 이벤트 루프의 구조

그 자체로 코 루틴은 다른 코 루틴에 대한 제어권을 양보하는 개념이 없습니다 . 코 루틴 스택의 맨 아래에있는 호출자에게만 제어권을 양보 할 수 있습니다. 이 호출자는 다른 코 루틴으로 전환하여 실행할 수 있습니다.

여러 코 루틴의이 루트 노드는 일반적으로 이벤트 루프입니다 . 일시 중단시 코 루틴은 재개하려는 이벤트 를 생성합니다 . 결과적으로 이벤트 루프는 이러한 이벤트가 발생하기를 효율적으로 기다릴 수 있습니다. 이를 통해 다음에 실행할 코 루틴 또는 재개하기 전에 대기하는 방법을 결정할 수 있습니다.

이러한 설계는 루프가 이해하는 사전 정의 된 이벤트 세트가 있음을 의미합니다. await마지막으로 이벤트가 await편집 될 때까지 여러 코 루틴이 서로 연결 됩니다. 이 이벤트는 제어 를 통해 이벤트 루프와 직접 통신 할 수 있습니다 yield.

loop -\
  :    \-> coroutine --await--> event --\
  :/ <-+----------------------- yield --/
  |    :
  |    :  # loop waits for event to happen
  |    :
  :\ --+-- send(reply) -------- yield --\
  :        coroutine <--yield-- event <-/

핵심은 코 루틴 서스펜션을 통해 이벤트 루프와 이벤트가 직접 통신 할 수 있다는 것입니다. 중간 코 루틴 스택은 필요하지 않습니다 어떤 루프를 실행중인 대한 지식이나 방법 이벤트 작업을.

2.1.1. 시간의 이벤트

처리하기 가장 간단한 이벤트는 특정 시점에 도달하는 것입니다. 이것은 스레드 코드의 기본 블록이기도합니다. 스레드 sleep는 조건이 참이 될 때까지 반복적으로 s입니다. 그러나 일반 sleep블록 실행 자체는 다른 코 루틴이 차단되지 않기를 원합니다. 대신 현재 코 루틴 스택을 재개해야하는시기를 이벤트 루프에 알려야합니다.

2.1.2. 이벤트 정의

이벤트는 단순히 열거 형, 유형 또는 기타 ID를 통해 식별 할 수있는 값입니다. 목표 시간을 저장하는 간단한 클래스로 이것을 정의 할 수 있습니다. 이벤트 정보 를 저장 하는 것 외에도 await클래스에 직접 허용 할 수 있습니다.

class AsyncSleep:
    """Event to sleep until a point in time"""
    def __init__(self, until: float):
        self.until = until

    # used whenever someone ``await``s an instance of this Event
    def __await__(self):
        # yield this Event to the loop
        yield self

    def __repr__(self):
        return '%s(until=%.1f)' % (self.__class__.__name__, self.until)

이 클래스 는 이벤트 만 저장 합니다. 실제로 이벤트를 처리하는 방법은 언급하지 않습니다.

유일한 특징은 __await__- 그것이 무엇 인 await에 대한 키워드 보인다. 실제로는 반복자이지만 일반 반복 기계에는 사용할 수 없습니다.

2.2.1. 이벤트를 기다리는 중

이제 이벤트가 생겼으니 코 루틴은 어떻게 반응할까요? 우리는 동등한 표현 할 수 있어야한다 sleep에 의해 await우리의 이벤트를 보내고. 무슨 일이 일어나고 있는지 더 잘보기 위해 절반의 시간 동안 두 번 기다립니다.

import time

async def asleep(duration: float):
    """await that ``duration`` seconds pass"""
    await AsyncSleep(time.time() + duration / 2)
    await AsyncSleep(time.time() + duration / 2)

이 코 루틴을 직접 인스턴스화하고 실행할 수 있습니다. 생성기와 유사하게 using coroutine.sendyield결과가 나올 때까지 코 루틴 을 실행합니다 .

coroutine = asleep(100)
while True:
    print(coroutine.send(None))
    time.sleep(0.1)

이것은 우리에게 두 개의 AsyncSleep이벤트를 제공 StopIteration하고 코 루틴이 완료되면 하나를 제공합니다. 유일한 지연은 time.sleep루프 에서 오는 것 입니다! 각각 AsyncSleep은 현재 시간의 오프셋 만 저장합니다.

2.2.2. 이벤트 + 수면

이 시점에서 우리는 두 가지 별도의 메커니즘을 사용할 수 있습니다.

  • AsyncSleep 코 루틴 내부에서 생성 될 수있는 이벤트
  • time.sleep 코 루틴에 영향을주지 않고 기다릴 수있는

특히,이 두 가지는 직교합니다. 어느 쪽도 다른쪽에 영향을 미치거나 트리거하지 않습니다. 결과적으로 .NET sleepFramework의 지연에 대처하기 위한 자체 전략을 마련 할 수 있습니다 AsyncSleep.

2.3. 순진한 이벤트 루프

코 루틴 이 여러 개인 경우 각 코 루틴이 깨어나고 싶은시기를 알 수 있습니다. 그런 다음 첫 번째 항목이 재개되기를 원할 때까지 기다린 다음 이후 항목을 기다릴 수 있습니다. 특히, 각 지점에서 우리는 다음 항목 에만 관심이 있습니다.

이것은 간단한 스케줄링을 만듭니다.

  1. 원하는 깨우기 시간으로 코 루틴 정렬
  2. 일어나고 싶은 첫 번째 선택
  3. 이 시점까지 기다려
  4. 이 코 루틴을 실행
  5. 1부터 반복합니다.

사소한 구현에는 고급 개념이 필요하지 않습니다. A list는 날짜별로 코 루틴을 정렬 할 수 있습니다. 기다리는 것은 규칙적 time.sleep입니다. 코 루틴을 실행하는 것은 coroutine.send.

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    # store wake-up-time and coroutines
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting:
        # 2. pick the first coroutine that wants to wake up
        until, coroutine = waiting.pop(0)
        # 3. wait until this point in time
        time.sleep(max(0.0, until - time.time()))
        # 4. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])

물론 이것은 개선의 여지가 충분합니다. 대기 큐에 힙을 사용하거나 이벤트에 디스패치 테이블을 사용할 수 있습니다. 또한 반환 값을 가져올 수 있습니다.StopIteration 코 루틴에 할당 할 수도 있습니다. 그러나 기본 원칙은 동일합니다.

2.4. 협력 대기

AsyncSleep이벤트와 run이벤트 루프는 타임 이벤트를 완벽하게 작업을 구현합니다.

async def sleepy(identifier: str = "coroutine", count=5):
    for i in range(count):
        print(identifier, 'step', i + 1, 'at %.2f' % time.time())
        await asleep(0.1)

run(*(sleepy("coroutine %d" % j) for j in range(5)))

이렇게하면 5 개의 코 루틴이 각각 협력 적으로 전환되어 0.1 초 동안 중단됩니다. 이벤트 루프는 동기식이지만 2.5 초가 아닌 0.5 초만에 작업을 실행합니다. 각 코 루틴은 상태를 유지하고 독립적으로 작동합니다.

3. I / O 이벤트 루프

지원하는 이벤트 루프 sleep폴링에 적합합니다 . 그러나 파일 핸들에서 I / O를 기다리는 것은보다 효율적으로 수행 할 수 있습니다. 운영 체제는 I / O를 구현하므로 어떤 핸들이 준비되었는지 알고 있습니다. 이상적으로 이벤트 루프는 명시 적 "I / O 준비"이벤트를 지원해야합니다.

3.1. select전화

Python에는 이미 OS에 읽기 I / O 핸들을 쿼리하는 인터페이스가 있습니다. 읽기 또는 쓰기 핸들과 함께 호출되면 읽기 또는 쓰기 준비 가 된 핸들을 반환합니다 .

readable, writeable, _ = select.select(rlist, wlist, xlist, timeout)

예를 들어, open쓸 파일을 작성하고 준비 될 때까지 기다릴 수 있습니다.

write_target = open('/tmp/foo')
readable, writeable, _ = select.select([], [write_target], [])

선택이 반환되면 writeable열린 파일이 포함됩니다.

3.2. 기본 I / O 이벤트

AsyncSleep요청과 마찬가지로 I / O에 대한 이벤트를 정의해야합니다. 기본 select로직에서 이벤트는 읽을 수있는 객체 (예 : open파일)를 참조해야 합니다. 또한 읽을 데이터의 양을 저장합니다.

class AsyncRead:
    def __init__(self, file, amount=1):
        self.file = file
        self.amount = amount
        self._buffer = ''

    def __await__(self):
        while len(self._buffer) < self.amount:
            yield self
            # we only get here if ``read`` should not block
            self._buffer += self.file.read(1)
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.file, self.amount, len(self._buffer)
        )

와 마찬가지로 AsyncSleep우리 대부분은 단지 기본 시스템 호출에 필요한 데이터를 저장합니다. 이번에 __await__는 원하는 amount내용을 읽을 때까지 여러 번 재개 할 수 있습니다. 또한 우리 return는 재개하는 대신 I / O 결과를 얻습니다.

3.3. 읽기 I / O로 이벤트 루프 확대

이벤트 루프의 기초는 여전히 run이전 에 정의 된 것입니다. 먼저 읽기 요청을 추적해야합니다. 이것은 더 이상 정렬 된 일정이 아니며 읽기 요청 만 코 루틴에 매핑합니다.

# new
waiting_read = {}  # type: Dict[file, coroutine]

이후 select.select시간 초과 매개 변수를, 우리는 대신에 사용할 수 있습니다 time.sleep.

# old
time.sleep(max(0.0, until - time.time()))
# new
readable, _, _ = select.select(list(reads), [], [])

이것은 우리에게 읽을 수있는 모든 파일을 제공합니다. 만약 있다면 해당 코 루틴을 실행합니다. 없는 경우 현재 코 루틴이 실행될 때까지 충분히 기다렸습니다.

# new - reschedule waiting coroutine, run readable coroutine
if readable:
    waiting.append((until, coroutine))
    waiting.sort()
    coroutine = waiting_read[readable[0]]

마지막으로 실제로 읽기 요청을 들어야합니다.

# new
if isinstance(command, AsyncSleep):
    ...
elif isinstance(command, AsyncRead):
    ...

3.4. 합치기

위의 내용은 약간 단순화되었습니다. 우리가 항상 읽을 수 있다면 잠자는 코 루틴을 굶지 않도록 약간의 전환을해야합니다. 우리는 읽을 것이 없거나 기다릴 것이없는 것을 처리해야합니다. 그러나 최종 결과는 여전히 30 LOC에 맞습니다.

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    waiting_read = {}  # type: Dict[file, coroutine]
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting or waiting_read:
        # 2. wait until the next coroutine may run or read ...
        try:
            until, coroutine = waiting.pop(0)
        except IndexError:
            until, coroutine = float('inf'), None
            readable, _, _ = select.select(list(waiting_read), [], [])
        else:
            readable, _, _ = select.select(list(waiting_read), [], [], max(0.0, until - time.time()))
        # ... and select the appropriate one
        if readable and time.time() < until:
            if until and coroutine:
                waiting.append((until, coroutine))
                waiting.sort()
            coroutine = waiting_read.pop(readable[0])
        # 3. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension ...
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])
        # ... or register reads
        elif isinstance(command, AsyncRead):
            waiting_read[command.file] = coroutine

3.5. 협력 I / O

AsyncSleep, AsyncReadrun구현은 이제 수면 및 / 또는 읽기에 완벽하게 작동합니다. 에서와 마찬가지로 sleepy읽기를 테스트하는 도우미를 정의 할 수 있습니다.

async def ready(path, amount=1024*32):
    print('read', path, 'at', '%d' % time.time())
    with open(path, 'rb') as file:
        result = return await AsyncRead(file, amount)
    print('done', path, 'at', '%d' % time.time())
    print('got', len(result), 'B')

run(sleepy('background', 5), ready('/dev/urandom'))

이를 실행하면 I / O가 대기 작업과 인터리브되는 것을 볼 수 있습니다.

id background round 1
read /dev/urandom at 1530721148
id background round 2
id background round 3
id background round 4
id background round 5
done /dev/urandom at 1530721148
got 1024 B

4. 비 블로킹 I / O

파일에 대한 I / O가 개념을 이해하는 동안에는 다음과 같은 라이브러리에는 적합하지 않습니다 asyncio. select호출은 항상 파일대해 반환 되며 둘 다 open및 둘 다 무기한 차단read 될 수 있습니다 . 이것은 이벤트 루프의 모든 코 루틴을 차단합니다. 스레드 및 동기화와 같은 라이브러리 는 파일의 비 차단 I / O 및 이벤트를 위조합니다.aiofiles

그러나 소켓은 비 차단 I / O를 허용하며 고유 한 대기 시간으로 인해 훨씬 ​​더 중요합니다. 이벤트 루프에서 사용될 때, 데이터를 기다리고 재 시도하는 것은 아무것도 차단하지 않고 래핑 될 수 있습니다.

4.1. 비 블로킹 I / O 이벤트

와 유사하게 AsyncRead소켓에 대한 일시 중지 및 읽기 이벤트를 정의 할 수 있습니다. 파일을 가져 오는 대신 소켓을 가져옵니다.이 소켓은 차단되지 않아야합니다. 또한, 우리의 __await__사용 socket.recv대신에 file.read.

class AsyncRecv:
    def __init__(self, connection, amount=1, read_buffer=1024):
        assert not connection.getblocking(), 'connection must be non-blocking for async recv'
        self.connection = connection
        self.amount = amount
        self.read_buffer = read_buffer
        self._buffer = b''

    def __await__(self):
        while len(self._buffer) < self.amount:
            try:
                self._buffer += self.connection.recv(self.read_buffer)
            except BlockingIOError:
                yield self
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.connection, self.amount, len(self._buffer)
        )

반면에 AsyncRead, __await__수행은 참으로 I / O를 비 차단. 데이터를 사용할 수 있으면 항상 읽습니다. 사용 가능한 데이터가 없으면 항상 일시 중지됩니다. 즉, 유용한 작업을 수행하는 동안에 만 이벤트 루프가 차단됩니다.

4.2. 이벤트 루프 차단 해제

이벤트 루프에 관한 한 큰 변화는 없습니다. 수신 대기 할 이벤트는으로 준비된 것으로 표시된 파일 설명자인 파일과 동일합니다 select.

# old
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
# new
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
    waiting_read[command.connection] = coroutine

이 시점에서, 그 명백해야 AsyncRead하고 AsyncRecv이벤트 같은 종류입니다. 교환 가능한 I / O 구성 요소 가있는 하나의 이벤트로 쉽게 리팩터링 할 수 있습니다 . 실제로 이벤트 루프, 코 루틴 및 이벤트 는 스케줄러, 임의의 중간 코드 및 실제 I / O를 명확하게 분리 합니다.

4.3. 논 블로킹 I / O의 추악한면

원칙적으로이 시점에서해야 할 일은 for 의 논리를 복제 read하는 것 recv입니다 AsyncRecv. 그러나 이것은 훨씬 더 추악합니다. 함수가 커널 내부에서 차단 될 때 조기 리턴을 처리해야하지만 제어권을 양보해야합니다. 예를 들어, 연결을 여는 것보다 파일을 여는 것이 훨씬 더 깁니다.

# file
file = open(path, 'rb')
# non-blocking socket
connection = socket.socket()
connection.setblocking(False)
# open without blocking - retry on failure
try:
    connection.connect((url, port))
except BlockingIOError:
    pass

간단히 말해서, 남은 것은 수십 줄의 예외 처리입니다. 이벤트와 이벤트 루프는 이미이 시점에서 작동합니다.

id background round 1
read localhost:25000 at 1530783569
read /dev/urandom at 1530783569
done localhost:25000 at 1530783569 got 32768 B
id background round 2
id background round 3
id background round 4
done /dev/urandom at 1530783569 got 4096 B
id background round 5

추가

github의 예제 코드


yield selfAsyncSleep에서 사용하면 Task got back yield오류가 발생하는데 그 이유는 무엇입니까? 나는 asyncio.Futures의 코드가 그것을 사용한다는 것을 알았습니다. 베어 수율을 사용하면 잘 작동합니다.
Ron Serruya 19

1
이벤트 루프는 일반적으로 자체 이벤트 만 예상합니다. 일반적으로 라이브러리간에 이벤트와 이벤트 루프를 혼합 할 수 없습니다. 여기에 표시된 이벤트는 표시된 이벤트 루프에서만 작동합니다. 구체적으로, asyncio는 이벤트 루프에 대한 신호로 None (즉, 베어 수율) 만 사용합니다. 이벤트는 이벤트 루프 객체와 직접 상호 작용하여 웨이크 업을 등록합니다.
MisterMiyagi

12

귀하의 corodesugaring하지만 약간 불완전, 개념적으로 올바른 것입니다.

await무조건 일시 중단되지는 않지만 차단 호출이 발생한 경우에만 중단됩니다. 통화가 차단되고 있음을 어떻게 알 수 있습니까? 이것은 대기중인 코드에 의해 결정됩니다. 예를 들어 대기 가능한 소켓 읽기 구현은 다음과 같이 정의 할 수 있습니다.

def read(sock, n):
    # sock must be in non-blocking mode
    try:
        return sock.recv(n)
    except EWOULDBLOCK:
        event_loop.add_reader(sock.fileno, current_task())
        return SUSPEND

실제 asyncio에서 동등한 코드Future매직 값을 반환 하는 대신 a의 상태를 수정 하지만 개념은 동일합니다. 제너레이터와 같은 객체에 적절하게 적용되면 위의 코드를 await편집 할 수 있습니다 .

발신자 측에서 코 루틴에 다음이 포함 된 경우 :

data = await read(sock, 1024)

설탕을 다음과 가까운 것으로 분해합니다.

data = read(sock, 1024)
if data is SUSPEND:
    return SUSPEND
self.pos += 1
self.parts[self.pos](...)

발전기에 익숙한 사람들은 위의 내용을 yield from자동으로 정지하는 방식으로 설명하는 경향이 있습니다.

서스펜션 체인은 이벤트 루프까지 계속됩니다.이 이벤트 루프는 코 루틴이 일시 중단되었음을 알아 차리고 실행 가능 집합에서 제거하고 실행 가능한 코 루틴이있는 경우 계속 실행합니다. 코 루틴을 실행할 수없는 경우 루프는 select()코 루틴이 관심있는 파일 설명자가 IO를위한 준비가 될 때까지 대기 합니다. (이벤트 루프는 파일 설명자 대 코 루틴 매핑을 유지합니다.)

위의 예에서 읽을 수 select()있는 이벤트 루프를 알리면 실행 가능한 집합에 sock다시 추가 coro되므로 일시 중단 지점부터 계속됩니다.

다시 말해:

  1. 기본적으로 모든 것이 동일한 스레드에서 발생합니다.

  2. 이벤트 루프는 코 루틴을 예약하고 기다리는 것이 무엇이든 (일반적으로 차단되는 IO 호출 또는 시간 초과) 준비가되면 깨어납니다.

코 루틴 구동 이벤트 루프에 대한 통찰력을 얻으 려면 Dave Beazley 의이 강연 을 추천 합니다 . 여기에서 라이브 청중 앞에서 이벤트 루프를 처음부터 코딩하는 방법을 보여줍니다.


고맙습니다. 이것은 제가 추구하는 것에 더 가깝지만, 이것은 왜 async.wait_for()그것이해야 할 일을 하지 않는지 설명 하지 않습니다 ... 왜 이벤트 루프에 콜백을 추가하고 그것을 알리는 것이 그렇게 큰 문제입니까? 방금 추가 한 콜백을 포함하여 필요한 많은 콜백을 처리하려면? 내 좌절감 asyncio은 부분적으로 기본 개념이 매우 간단하다는 사실 때문이며, 예를 들어 Emacs Lisp는 유행어를 사용하지 않고 오랫동안 구현했습니다 ... (즉 create-async-process, accept-process-output그리고 이것이 필요한 전부입니다 ... (계속)
wvxvw

10
@wvxvw 마지막 단락에만 6 개의 질문이 포함되어 있다는 점을 감안할 때 가능한 한 많이 게시 한 질문에 대답 할 수있는 한 많이했습니다. 그래서 우리는 계속합니다. 그것은 그것이 wait_for 해야 할 일하지 않는 것이 아닙니다 ( 그렇습니다 , 그것은 여러분이 기다려야하는 코 루틴입니다). 그것은 여러분의 기대가 시스템이하기 위해 설계되고 구현 된 것과 일치하지 않는다는 것입니다. 이벤트 루프가 별도의 스레드에서 실행되는 경우 문제가 asyncio와 일치 할 수 있다고 생각하지만 사용 사례에 대한 세부 정보를 모르고 솔직히 당신의 태도가 당신을 돕는 것을별로 재미있게 만들지 않습니다.
user4815162342

5
@wvxvw- My frustration with asyncio is in part due to the fact that the underlying concept is very simple, and, for example, Emacs Lisp had implementation for ages, without using buzzwords...파이썬에 대한 유행어없이이 간단한 개념을 구현하는 것을 막을 수있는 것은 없습니다. :) 왜이 못생긴 asyncio를 사용합니까? 처음부터 직접 구현하십시오. 예를 들어, async.wait_for()원하는대로 정확히 수행하는 자체 함수를 만드는 것으로 시작할 수 있습니다 .
Mikhail Gerasimov

1
@MikhailGerasimov 당신은 그것이 수사학적인 질문이라고 생각하는 것 같습니다. 그러나 나는 당신을 위해 수수께끼를 풀고 싶습니다. 언어는 다른 사람과 대화하도록 설계되었습니다. 나는 다른 사람들이 말하는 언어를 선택할 수 없습니다. 그들이 말하는 언어가 쓰레기라고 믿더라도 제가 할 수있는 최선은 그들에게 그 사실을 설득하는 것입니다. 즉, 내가 자유롭게 선택할 수 있다면 asyncio. 그러나 원칙적으로 그것은 내 결정이 아닙니다. en.wikipedia.org/wiki/Ultimatum_game을 통해 가비지 언어를 사용하도록 강요 받았습니다 .
wvxvw

4

모두 asyncio가 해결하는 두 가지 주요 과제로 요약됩니다.

  • 단일 스레드에서 여러 I / O를 수행하는 방법은 무엇입니까?
  • 협력 적 멀티 태스킹을 구현하는 방법은 무엇입니까?

첫 번째 요점에 대한 답은 오랫동안 주변에 있었으며 선택 루프 라고합니다 . 파이썬에서는 selectors module 에서 구현됩니다 .

두 번째 질문은 코 루틴 의 개념 , 즉 실행을 중지하고 나중에 복원 할 수있는 함수와 관련이 있습니다. 파이썬에서 코 루틴은 생성기yield from 문을 사용하여 구현됩니다 . 이것이 async / await 구문 뒤에 숨어있는 것입니다 .

답변에 더 많은 리소스 .


편집 : 고 루틴에 대한 의견 해결 :

asyncio의 goroutine에 가장 가까운 것은 실제로 코 루틴이 아니라 작업입니다 ( 문서 의 차이점 참조 ). 파이썬에서 코 루틴 (또는 생성기)은 이벤트 루프 또는 I / O의 개념에 대해 아무것도 모릅니다. 단순히 yield현재 상태를 유지하면서 실행을 멈출 수있는 기능 이므로 나중에 복원 할 수 있습니다. yield from구문은 투명한 방식으로 체인 수 있습니다.

이제 asyncio 작업 내에서 체인 맨 아래에있는 코 루틴은 항상 미래를 산출합니다 . 이 미래는 이벤트 루프로 거품을 일으키고 내부 기계에 통합됩니다. 퓨처가 다른 내부 콜백에 의해 완료되도록 설정되면 이벤트 루프는 퓨처를 코 루틴 체인으로 다시 보내 작업을 복원 할 수 있습니다.


수정 : 게시물의 몇 가지 질문에 답하기 :

이 시나리오에서 I / O는 실제로 어떻게 발생합니까? 별도의 스레드에서? 전체 통역사가 정지되고 I / O가 통역사 외부에서 발생합니까?

아니요, 스레드에서는 아무 일도 일어나지 않습니다. I / O는 항상 파일 설명자를 통해 이벤트 루프에 의해 관리됩니다. 그러나 이러한 파일 설명 자의 등록은 일반적으로 높은 수준의 코 루틴에 의해 숨겨져 더티 작업을 수행합니다.

I / O는 정확히 무엇을 의미합니까? 내 파이썬 프로 시저가 C open () 프로 시저를 호출하고 커널에 인터럽트를 전송하여 제어를 포기한다면, 파이썬 인터프리터는이를 어떻게 알고 다른 코드를 계속 실행할 수 있으며 커널 코드는 실제 I /를 수행합니다. O 그리고 원래 인터럽트를 보낸 Python 절차를 깨울 때까지? 원칙적으로 파이썬 인터프리터는 이런 일이 일어나는 것을 어떻게 알 수 있습니까?

I / O는 모든 차단 호출입니다. asyncio에서는 모든 I / O 작업이 이벤트 루프를 거쳐야합니다. 말씀하신 것처럼 이벤트 루프는 일부 동기 코드에서 차단 호출이 수행되고 있음을 알 수있는 방법이 없기 때문입니다. 즉 open, 코 루틴 컨텍스트 내에서 동기식을 사용해서는 안됩니다 . 대신 .NET Framework 의 비동기 버전을 제공하는 aiofile 과 같은 전용 라이브러리를 사용하십시오 open.


코 루틴이를 사용하여 구현 yield from되었다고 말하는 것은 실제로 아무 말도하지 않습니다. yield from구문 구조 일 뿐이며 컴퓨터가 실행할 수있는 기본적인 구성 요소는 아닙니다. 마찬가지로, 루프를 선택합니다. 예, Go의 코 루틴도 선택 루프를 사용하지만 제가하려는 것은 Go에서 작동하지만 Python에서는 작동하지 않습니다. 왜 작동하지 않았는지 이해하려면 더 자세한 답변이 필요합니다.
wvxvw

미안 ... 아니, 아니. "미래", "작업", "투명한 방법", "수익"은 단지 유행어이며 프로그래밍 영역의 개체가 아닙니다. 프로그래밍에는 변수, 절차 및 구조가 있습니다. 따라서 "고 루틴은 작업이다"라는 말은 질문을 던지는 순환 적 진술 일뿐입니다. 궁극적으로, 무엇을하는지에 대한 설명은 asyncio파이썬 구문이 번역 된 것을 보여주는 C 코드로 요약됩니다.
wvxvw

귀하의 답변이 내 질문에 대한 답변이 아닌 이유를 더 설명하기 위해 : 귀하가 제공 한 모든 정보에 대해 링크 된 질문에 게시 한 코드의 시도가 작동하지 않은 이유를 알 수 없습니다. 이 코드가 작동하는 방식으로 이벤트 루프를 작성할 수 있다고 확신합니다. 사실, 이것이 내가 작성해야한다면 이벤트 루프를 작성하는 방법 일 것입니다.
wvxvw

7
@wvxvw 동의하지 않습니다. 그것들은 "버즈 워드"가 아니라 많은 라이브러리에서 구현 된 고급 개념입니다. 예를 들어, asyncio 작업, gevent greenlet 및 goroutine은 모두 단일 스레드 내에서 동시에 실행될 수있는 실행 단위에 해당합니다. 또한 파이썬 생성기의 내부 작업에 들어가기를 원하지 않는 한 asyncio를 이해하는 데 C가 전혀 필요하지 않다고 생각합니다.
Vincent

@wvxvw 두 번째 편집을 참조하십시오. 이렇게하면 몇 가지 오해를 해결할 수 있습니다.
Vincent
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.