1 분 소요

예전에 만들어진 소스를 분석 중 boost::asio::io_service::work 가 나오길래 무엇인지 찾아보았다.

2026년 수정 안내 “작업이 없으면 강제 종료되는 것을 막기 위한 장치”라는 원래 설명이 부정확했다. io_service는 강제 종료되지 않으며, run()이 조용히 반환될 뿐이다. 그리고 io_service/work 자체가 Boost 1.66(2017)부터 deprecated되어, 지금 새로 쓴다면 io_context/executor_work_guard를 써야 한다. 둘 다 정리한다.

work가 막아주는 것 : “강제 종료”가 아니라 “run()의 조기 반환”

io_service::run()처리할 작업이 하나도 남지 않으면 즉시 반환된다. 대기 중인 비동기 작업(펜딩 핸들러)이 없으면 io_service는 “할 일이 없다”고 판단하고 run()을 빠져나온다.

문제는 서버처럼 당장은 할 일이 없어도 앞으로 들어올 작업을 계속 기다려야 하는 경우다. 예를 들어 아직 아무 연결도 안 들어온 시점에 run()을 호출하면, 등록된 핸들러가 없으므로 run()이 곧바로 반환되고 그 워커 스레드는 io_service를 처리할 수 없는 상태가 된다.

io_service::work 객체를 하나 만들어두면, io_service는 “아직 끝나지 않은 작업이 있다”고 판단해 run()이 반환되지 않고 계속 대기한다. work 객체가 소멸되어야 비로소 할 일이 없다는 판단으로 돌아간다.

boost::asio::io_service io_service;
boost::asio::io_service::work work( io_service );   // run()이 즉시 반환되지 않도록 붙잡아둔다

std::thread worker( [&io_service]() { io_service.run(); } );

// ... 나중에 들어오는 비동기 작업들이 io_service에 계속 등록되어 처리된다 ...

“강제 종료를 막는다”는 표현은 부정확하다. 프로세스나 io_service가 죽는 것이 아니라, run()이 할 일이 없다고 판단해 예상보다 일찍 반환되는 것을 막아주는 것이다.

Boost 1.66부터는 io_context / executor_work_guard

Boost 1.66(2017년 12월)에서 Asio가 크게 개편되면서 다음처럼 이름이 바뀌었다.

예전 (deprecated) 현재
boost::asio::io_service boost::asio::io_context
boost::asio::io_service::work boost::asio::executor_work_guard (make_work_guard로 생성)
#include <boost/asio.hpp>

boost::asio::io_context io_context;

// work 대신 이렇게 만든다. RAII로 소멸자에서 자동 해제된다.
auto work_guard = boost::asio::make_work_guard( io_context );

std::thread worker( [&io_context]() { io_context.run(); } );

// ... 비동기 작업 등록 ...

work_guard.reset();   // 더 이상 일감이 없으면 여기서 명시적으로 놓아준다

executor_work_guardwork와 역할이 같지만, reset()으로 명시적으로 놓아주거나 스코프를 벗어나면 자동으로 해제된다는 점이 더 명확하다.

표준 C++에 채택된 std::execution(C++26 예정)이나 여러 네트워킹 라이브러리도 Asio의 이 실행자(executor) 모델을 기반으로 하고 있어, 이 개념을 알아두면 다른 곳에서도 도움이 된다.

참고자료

댓글 남기기