뭐하는 놈인지는 알고 쓰자 시리즈의 두번째 주제는 boost io_context::run 함수를 실행 상태로 유지하는 방법입니다.

해당 포스팅에서는 io_context를 사용하기 위해서는 반드시 run 함수를 호출해야한다는 주입식(?) 교육에서 벗어나고자 boost io_context::run 함수에 대해 간략히 알아보고, 우리의 최종 목표인 io_context에 당장에 할당된 작업이 없더라도 io_context::run 함수를 실행 상태로 유지하는 방법에 대해 알아보도록 하겠습니다. Prevent io_context::run from returning 사이트의 내용을 바탕으로 작성되었으니 참고부탁드립니다.


목차

  1. 1. io_context::run 함수란?
  2. 2. io_context::run 함수가 반환되지 않도록 하는 방법
  3. 3. 마치며

io_context::run 함수란?

먼저 Boost 공식 매뉴얼의 내용을 참고하여 io_context::run 함수에 대해 간략히 알아보도록 하겠습니다.


io_context::run 함수는 io_context 객체의 이벤트 처리 루프를 실행합니다.

Run the io_context object’s event processing loop.

run() 함수는 모든 작업이 완료되거나 더이상 처리할 핸들러가 없을때까지 블록상태를 유지합니다.

The run() function blocks until all work has finished and there are no more handlers to be dispatched, or until the io_context has been stopped.

io_context가 핸들러를 실행하는 공간인 쓰레드 풀을 설정하기 위해 다중 쓰레드에서도 run() 함수를 호출할 수 있습니다. 쓰레드 풀에서 대기 중인 쓰레드들은 모두 동등한 자격을 가지며 io_context는 핸들러를 실행시키기위해 임의로 그들중 한개를 선택하게됩니다.

Multiple threads may call the run() function to set up a pool of threads from which the io_context may execute handlers. All threads that are waiting in the pool are equivalent and the io_context may choose any one of them to invoke a handler.

run() 함수로 부터 빠져나오기 위한 일반적인 방법은 io_context 객체가 종료되도록하는 것입니다. run(), run_one(), poll() 또는 poll_one() 함수를 restart() 함수 호출없이 연속해서 호출하는 경우에는 곧바로 해당 함수를 빠져나오게 될 것입니다.

A normal exit from the run() function implies that the io_context object is stopped (the stopped() function returns true). Subsequent calls to run(), run_one(), poll() or poll_one() will return immediately unless there is a prior call to restart().

여기서 저희가 주목할 내용은 io_context::run 함수는 (의도적인 종료 함수의 호출을 제외하고는) io_context 상의 모든 작업이 종료되고, 더이상 io_context에 할당(dispatch)된 핸들러가 없을때까지만 블록(block)상태를 유지한다는 것입니다.


io_context::run 함수가 반환되지 않도록 하는 방법

그렇다면 io_context에 당장에 할당할 작업은 없더라도 run 함수를 블록상태로 유지하기 위해서는 어떻게 해야할까요? 여기서 부터는 원문 포스팅에 대한 번역 내용입니다.


io_context::run은 모든 예약된 작업이 완료될때까지 실행 상태를 유지합니다. 모든 작업이 완료된 이후에 io_context::run은 반환될것이며, 해당 함수를 호출한 쓰레드는 블록이 해제될 것입니다.

io_context::run runs until all scheduled tasks are completed. After that io_context::run will return and the caller thread will unblock:

  • cpp
1
2
3
4
boost::asio::io_context io_context;
// Schedule some tasks
io_context.run();
std::cout << "Job's done! Continue the execution\n";

하지만 때때로 수행할 작업의 유무에 관계없이 io_context::run의 실행 상태를 유지해야하는 경우가 있습니다. 지금까지 살펴보았던 서버들은 항상 async_accept를 수행중이었기때문에 적어도 하나의 예약된 작업을 계속해서 가지고 있었습니다. 그렇기때문에 우리는 특정한 방법을 활용하여 그들을 실행중인 상태로 유지해야할 필요가 없었습니다.

However sometimes you may need to keep it running regardless if there are tasks to execute or not. Servers we’ve reviewed so far were always doing async_accept, so they always had at least one task scheduled, so we didn’t really need to keep them running in such a way.

하지만 클라이언트는 async_accept 작업을 수행하지 않으며, 그것의 입장에서 어떠한 특정 시점에 예약된 작업이 없다는 것은 지극히 일반적인 상황입니다. 이러한 상황에서 io_context::run 함수가 반환되는 것을 막기 위해 여러분은 boost::asio::executor_work_guard 클래스 인스턴스를 사용해야 합니다. 그것의 이름이 너무 길어지는 관계로 별칭(alias)을 사용하도록 하겠습니다.

However a client doesn’t do async_accept and it’s a normal thing for it not to have scheduled tasks at some point at all. To prevent io_context::run from returning you should use boost::asio::executor_work_guard(a former io_context::work which is currently deprecated) class instance. Its name is too long, so let’s alias it right away:

  • cpp
1
2
3
4
5
6
7
using work_guard_type = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>;

boost::asio::io_context io_context;
work_guard_type work_guard(io_context.get_executor());
// Schedule some tasks or not
io_context.run();
std::cout << "Sorry, we'll never reach this!\n";

여러분은 여전히 어플리케이션을 강제로 혹은 우아하게 종료하기 위한 방법이 필요합니다. 이를 위해 io_context::stop 함수를 사용할 수 있습니다.

You still need a way to stop your application somehow, and to stop it gracefully. You may use io_context::stop function:

  • cpp
1
2
3
4
5
6
7
8
9
10
boost::asio::io_context io_context;
work_guard_type work_guard(io_context.get_executor());
// Schedule some tasks or not
std::thread watchdog([&]
{
std::this_thread::sleep_for(10s);
io_context.stop(); // That's OK, io_context::stop is thread-safe
});
io_context.run();
std::cout << "We stopped after 10 seconds of running\n";

위 경우에 io_context::run 함수는 그 즉시 종료되지는 않을 것이지만 가장 적절한 시점에 종료될 것이며 남아있는 예약 작업들은 버려지게 될 것입니다. 이것은 정확히 우리가 원하던 동작일 겁니다.

In that case io_context::run won’t stop right away but do this in the nearest suitable point of time, and the rest of scheduled tasks will be discarded. And that may be exactly what you’re needed.

여러분은 또한 예약된 모든 작업이 완료될때까지 대기하다 그 이후에 io_context::run 함수가 반환되길 원할수도 있습니다. 이를위해 io_context::work 클래스 인스턴스를 파괴해주기만 하면 됩니다. 파괴하는 연산 또한 쓰레드에 안전합니다.

You may also need to wait until all scheduled tasks are completed and return from io_context::run after that. To do so you just need to destroy io_context::work class instance. This operation is also thread-safe:

  • cpp
1
2
3
4
5
6
7
8
9
10
boost::asio::io_context io_context;
auto work_guard = std::make_unique<work_guard_type>(io_context.get_executor());
// Schedule some tasks or not
std::thread watchdog([&]
{
std::this_thread::sleep_for(10s);
work_guard.reset(); // Work guard is destroyed, io_context::run is free to return
});
io_context.run();
std::cout << "We stopped after 10+ seconds of running\n";

만약 io_context::run 함수가 반환된 후 그것을 다시 호출하고 싶다면 그 전에 io_context::restart 함수를 호출해야합니다.

If you’re going to call io_context::run once again after it returned, then you should call io_context::restart before that.

  • cpp
1
2
3
4
5
6
boost::asio::io_context context;
boost::asio::post(context, [](){ std::cout << "hello boost::asio::post in context first" << std::endl; });
temp_context.run();
boost::asio::post(temp_context, [](){ std::cout << "hello boost::asio::post in context second" << std::endl; });
temp_context.restart();
temp_context.run();

마치며

지금까지 io_context에 당장에 할당된 작업이 없더라도 io_context::run 함수를 실행 상태로 유지하는 방법에 대해 알아보았습니다. 아래와 같이 정리해보면서 포스팅을 마치도록 하겠습니다.

  • io_context에 할당된 작업이 없으면 io_context::run 함수는 즉시 반환된다.
  • io_context에 당장에 할당된 작업이 없는 상황에서 io_context::run 함수가 반환되는 것을 막기 위해서는 boost::asio::executor_work_guard 클래스 인스턴스를 사용하라.
  • io_context::run 함수가 반환된 후 다시 한번 호출하고 싶다면 io_context::restart 함수를 먼저 호출하라.


해당 게시글에서 발생한 오탈자나 잘못된 내용에 대한 정정 댓글 격하게 환영합니다😎

Reference