NeuroWhAI의 잡블로그

[Rust] Async 공부... 본문

개발 및 공부/언어

[Rust] Async 공부...

NeuroWhAI 2019. 8. 8. 21:03


https://rust-lang.github.io/async-book/02_execution/01_chapter.html

 

Under the Hood: Executing Futures and Tasks - Asynchronous Programming in Rust

In this section, we'll cover the underlying structure of how Futures and asynchronous tasks are scheduled. If you're only interested in learning how to write higher-level code that uses existing Future types and aren't interested in the details of how Futu

rust-lang.github.io

trait Future : 비동기 연산을 표현. async 함수는 자동으로 이것을 impl하고 있다?
ㄴ fn poll : Executor에 의해서 호출되는 함수. 값이 준비되면 반환하고 아니라면 Context 파라미터 안의 Waker를 저장해두었다가 작업이 완료되었을 때 호출.

struct Waker
ㄴ fn wake : 연관된 Task를 깨움. 보통 Executor에게 요청하여 연관된 Future의 poll 호출을 예약하도록 구현.

struct Context : 비동기 작업의 문맥. Future의 poll 호출시 넘겨지며 현재는 Waker를 얻는 것 말곤 용도 없음.
ㄴ fn waker : 가지고 있는 Waker의 참조를 반환한다.

보았던 예시에선 Task라는 것을 추가로 만들고 Future를 저장할 수 있도록 함.
또, Task 자체에서 Waker를 만들도록 했는데 외부 라이브러리를 사용해서 자세한 건 모르겠음.
위 3개의 구성 요소만으론 연결고리가 끊기는 지점이 있는데 그 부분을 위해 Future와 Waker을 연결해주는 것으로 추정.
(Waker가 Executor에 poll 호출 예약을 요청해도 Executor는 그 Waker와 연관된 Future가 뭔지 모르기 때문.)

처음 Future가 만들어질 때 바로 poll을 호출하여 Future가 Waker를 따로 저장해둘 수 있도록 함.
(예시에선 Executor와의 통신 채널을 써서 바로 poll 호출을 예약함.)
Future는 작업이 끝나는 등의 적절한? 상황에 저장하고 있던 Waker의 wake를 호출한다.
예시에서는 이것이 Task의 어떤 메소드를 호출하고 거기서 채널을 통해 Executor로 자신을 넘긴다.
Executor는 그 채널을 사용해서 Task를 받는다.
Task를 받고 여기서 Waker를 만들고 또 여기서 Context를 만듦.
Task의 Future를 꺼내 Context와 함께 poll을 호출.
호출된 poll은 현재 작업 상태를 확인하고 완료되었다면 Ready(result)를 반환하고 아니라면 Pending을 반환.
Executor는 이 반환값을 받고 Pending일 경우 Future를 다시 Task에 돌려줌.
예시의 경우엔 비동기 작업에 반환값이 없었기 때문에 Ready(T)는 별도로 처리하지 않았는데 실제론 어떻게..?

또, 궁금한 것은 Task는 처음에 만들어지고 바로 채널로 이동된 후 Executor에서 처리되는데 여기서 Pending시 별도로 Task를 다시 어디 넣거나 하는 작업이 없다.
Task는 실제론 Arc고 참조 Arc 포인터가 전부 사라지니 소멸해야하는 게 맞는 것 아닌가?
Waker인가? 외부 라이브러리에서 이뤄지는 작업이라 불분명하지만 Task에서 Waker를 만든다고 했고 이 Waker를 Future가 내부적으로 저장하고 있으니 만약 Waker 안에서 Task에 대한 Arc 포인터를 유지하고 있다면 소멸하진 않을 것이다.
근데 그럼 순환참조 아닌가?
Task도 Box로 Future를 가지고 있는데 Future도 Waker를 거쳐 Task를 가지고 있다는 것이 되는데;;
흠... 아님 애초에 순환참조로 비동기 작업의 수명을 유지시키고 있나?
위에서 Executor가 Task의 Future를 '꺼낸다'라고 했는데 실제로 소유권을 빼내서 로컬 라이프타임으로 바꾼다는 것임.
Task가 Box로 Future를 가지고 있다고 했지만 또 사실은 Option<Box>이기 때문에 take로 빼오는 것이 가능함.
이렇게 빼오고 Pending시 Future를 다시 Task에 돌려준다고도 했다.
그럼 여전히 순환참조 상태인데 만약 작업 완료를 뜻하는 Ready(T)였다면?
로컬 변수에 담긴 Future는 블럭이 끝나면 소멸할 것이고 Future가 유지하고 있을지도 모른다고 했던 Task에 대한 Arc도 소멸할 것이다.
그럼 결국 해당 비동기 작업과 연관된 Future, Task가 모두 올바르게 정리되는 것 같긴 한데...
흠...



Comments