boost io_context에 대한 고찰
뭐하는 놈인지는 알고 쓰자 시리즈의 첫번째 주제는 boost io_context
입니다.
이번 기회에 boost 라이브러리를 사용하다보면 한번쯤은 마주치게되는 io_context에 대해 생각해보는 시간을 갖겠습니다.
목차
Boost Asio
우선 io_context에 대해 이야기하기 전에 Boost Asio를 간략하게 소개하고 넘어가도록 하겠습니다. boost 공식 홈페이지의 내용을 참고하여 작성하였으니 원문 내용은 여기를 참조해주시기 바랍니다.
Boost.Asio는 쓰레드와 명시적 Locking을 기반으로한 동시성 모델의 사용을 프로그램에게 요구하지 않으면서 느린 I/O operation을 관리하는 도구를 제공합니다. (이는 Boost.Asio 내부적으로 동시성 처리를 할거니깐 사용자들은 걱정말고 사용하라는 의미로도 볼 수 있죠.)
Boost.Asio provides the tools to manage these long running operations, without requiring programs to use concurrency models based on threads and explicit locking.
소켓을 예로 들어 Boost.Asio에서 사용하는 용어들에 대해 조금 더 알아보도록 하겠습니다.
동기 연결
여러분의 프로그램은 적어도 한 개 이상의 (boost::asio::io_context나 boost::asio::thread_pool 또는 boost::asio::system_context 객체와 같은)I/O execution context를 가지고 있을 것입니다. I/O execution context는 OS의 I/O 서비스에 대한 프로그램의 링크를 나타냅니다.
Your program will have at least one I/O execution context, such as an boost::asio::io_context object, boost::asio::thread_pool object, or boost::asio::system_context. This I/O execution context represents your program‘s link to the operating system‘s I/O services.
1 | boost::asio::io_context io_context; |
- I/O object(여기서는 socket)에서 connect 호출
1
2boost::asio::ip::tcp::socket socket(io_context);
socket.connect(server_endpoint); - I/O object는 I/O execution context에게 요청 전달
- I/O execution context는 connect 동작을 수행하기 위해 OS 호출
- OS는 I/O execution context에게 수행 결과를 리턴
- I/O execution context는 boost::system::error_code에 수행 결과 발생한 에러를 해석하고 이를 I/O object에게 다시 전달
- I/O object는 수행에 실패했을 경우 예외를 던짐
비동기 연결
I/O object에서 async_connect 호출(your_completion_handler는 함수 또는 함수 객체임)
1
2void your_completion_handler(const boost::system::error_code& ec);
socket.async_connect(server_endpoint, your_completion_handler);I/O object는 I/O execution context에게 요청 전달
I/O execution context는 async_connect 동작을 수행하기 위해 OS 호출(동기 연결과 다르게 async_connect의 수행 결과를 기다리지는 않음)
- OS는 큐에 처리 결과를 추가함으로써 async_connect 수행이 완료됐다는 것을 알림
- I/O execution context로 io_context를 사용하는 경우에는 io_context::run()함수를 반드시 호출해야함. io_context::run()함수는 완료되지 않은 비동기 수행이 있는 경우에는 block 상태이므로 대게 최초 비동기 수행을 시작하자마자 해당 함수를 호출함
- io_context::run()함수 내에서 I/O execution context는 큐에 추가된 수행 결과들을 뽑아내어 error_code에 해석 결과를 저장하고 handler 함수에게 전달함
동기 및 비동기 소켓 연결에 대한 내용을 바탕으로 io_context의 개념을 정리해보면 다음과 같습니다.
유저 공간에서 커널에 접근하기 위한 시스템 콜을 날리려고 할때 실제 커널과의 통신을 중계하는 주체이다. 즉, 실제 시스템 콜 함수가 실행되는 공간이라고 생각하면 된다.
Execution Context
io_context는 I/O 작업을 위한 매개체이자 일종의 작업 공간이라는건 어느정도 느낌이 온 것같습니다. 이제는 io_context를 포함하는 개념인 execution context에 대해 조금 더 알아보도록 하겠습니다. (해당 내용은 Executors and Asynchronous Operations를 참조하여 작성하였습니다.)
execution context는 함수 객체가 실행되는 공간입니다.
- An execution context is a place where function objects are executed.
- Examples
- A fixed-size thread pool
- A loop scheduler
- An asio::io_context
- The set of all threads in the process
말그대로 함수 객체(우리가 흔히 핸들러라고 부르는)가 실제로 실행되는 공간이라는 겁니다. 대표적인 execution context인 io_context와 thread pool 사용 예제를 한번 살펴봅시다.
- cpp
1 |
|
위 코드는 boost::asio::post 함수를 사용하여 람다 식으로 정의된 함수 객체를 위에서 정의한 io_context에서 실행시키겠다는 내용입니다. (람다 식은 closure라는 이름없는 함수 객체를 생성해줍니다. 람다 식의 정의는 여기를 참고하세요.)
아 그리고 post 함수 아래 io_context의 run 함수는 함수 객체를 실행시킬 공간(여기서는 io_context)을 활성화시켜주는 역할을 한다고 생각하시면 될 것같습니다. (run 함수를 실행하지 않고서는 람다 식이 실행되지 않습니다!)
아래 코드는 마찬가지로 boost::asio::post 함수를 사용하여 이번에는 직접 정의한 함수 test_function을 io_context가 아닌 thread_pool(또 다른 execution context들 중 하나죠)에서 실행시키겠다는 내용입니다.
- cpp
1 |
|
마치며
지금까지 io_context를 시작으로 execution context라는 개념을 조금이나마 구체화하는 시간을 가져봤습니다. 한줄로 정리하며 마치겠습니다.
Execution Context는 함수 또는 함수 객체가 실행되는 공간이다.
해당 게시글에서 발생한 오탈자나 잘못된 내용에 대한 정정 댓글 격하게 환영합니다😎
Reference
- https://www.boost.org/doc/libs/1_78_0/doc/html/boost_asio/overview.html
- https://www.boost.org/doc/libs/1_78_0/doc/html/boost_asio/overview/rationale.html
- https://www.boost.org/doc/libs/1_78_0/doc/html/boost_asio/overview/core/basics.html
- https://think-async.com/executors/Executors_and_Asynchronous_Operations_Slides.pdf
- https://en.cppreference.com/w/cpp/language/lambda