기초
간단한 예제로 시작하여 관련 Boost.Asio 부분을 살펴 보겠습니다.
void handle_async_receive(...) { ... }
void print() { ... }
...
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
...
io_service.post(&print);
socket.connect(endpoint);
socket.async_receive(buffer, &handle_async_receive);
io_service.post(&print);
io_service.run();
처리기 는 무엇입니까 ?
핸들러는 콜백에 불과하다. 예제 코드에는 3 개의 핸들러가 있습니다.
print
핸들러 (1).
handle_async_receive
처리기 (3).
print
처리기 (4).
동일한 print()
함수가 두 번 사용 되더라도 각 용도는 고유하게 식별 가능한 처리기를 만드는 것으로 간주됩니다. 핸들러는 위와 같은 기본 함수부터 생성 된 펑터 boost::bind()
및 람다와 같은 더 복잡한 구조에 이르기까지 다양한 모양과 크기로 제공 될 수 있습니다 . 복잡성에 관계없이 핸들러는 여전히 콜백에 지나지 않습니다.
무엇인가 일이 ?
작업은 Boost.Asio가 애플리케이션 코드를 대신하여 수행하도록 요청 된 일부 처리입니다. 때때로 Boost.Asio는 그것에 대해 말하자마자 일부 작업을 시작할 수 있으며 다른 경우에는 나중에 작업을 수행하기 위해 기다릴 수 있습니다. 작업이 완료되면 Boost.Asio는 제공된 핸들러 를 호출하여 애플리케이션에 알립니다 .
Boost.Asio는 보장 핸들러 만 현재 호출 스레드 내에서 실행됩니다 run()
, run_one()
, poll()
, 또는 poll_one()
. 이것들은 작업을 수행하고 핸들러를 호출하는 스레드입니다 . 따라서 위의 예에서는 (1)에 print()
게시 될 때 호출되지 않습니다 io_service
. 대신에 추가되고 io_service
나중에 호출됩니다. 이 경우 io_service.run()
(5) 이내 입니다.
비동기 작업이란?
비동기 작업은 작업을 생성하고 Boost.Asio는 호출합니다 핸들러를 작업이 완료되면 응용 프로그램을 알립니다. 비동기 작업은 접두사가있는 이름이있는 함수를 호출하여 생성됩니다 async_
. 이러한 기능을 시작 기능 이라고도 합니다 .
비동기 작업은 세 가지 고유 한 단계로 나눌 수 있습니다.
io_service
작업을 수행해야하는 관련자 를 시작하거나 알립니다 . async_receive
작업 (3)을 알려 io_service
그 다음, 소켓에서 비동기 적으로 읽어 데이터를해야 함을 async_receive
즉시 반환합니다.
- 실제 작업을 수행합니다. 이 경우
socket
데이터를 수신하면 바이트를 읽고 buffer
. 실제 작업은 다음 중 하나에서 수행됩니다.
- 시작 함수 (3), Boost.Asio가 차단하지 않을 것이라고 결정할 수있는 경우.
- 응용 프로그램이 명시 적으로 실행할 때
io_service
(5).
handle_async_receive
ReadHandler를 호출합니다 . 다시 한번 말하지만 핸들러 는 io_service
. 따라서 작업이 완료되는 시점 (3 또는 5)에 관계없이 (5) handle_async_receive()
내에서만 호출됩니다 io_service.run()
.
이 세 단계 사이의 시간과 공간의 분리를 제어 흐름 반전이라고합니다. 비동기 프로그래밍을 어렵게 만드는 복잡성 중 하나입니다. 그러나 코 루틴 사용과 같이이를 완화하는 데 도움이되는 기술이 있습니다 .
무엇을합니까 io_service.run()
?
스레드가를 호출하면 이 스레드 내에서 io_service.run()
작업 및 핸들러 가 호출됩니다. 위의 예에서 io_service.run()
(5)는 다음 중 하나가 될 때까지 차단됩니다.
- 두
print
핸들러 모두에서 호출 및 리턴되었으며 수신 조작은 성공 또는 실패로 완료되고 해당 handle_async_receive
핸들러가 호출 및 리턴되었습니다.
- 을
io_service
통해 명시 적으로 중지됩니다 io_service::stop()
.
- 핸들러 내에서 예외가 발생합니다.
잠재적 인 의사 흐름은 다음과 같이 설명 할 수 있습니다.
io_service 생성
소켓 생성
io_service에 인쇄 핸들러 추가 (1)
소켓이 연결될 때까지 기다립니다 (2).
io_service에 비동기 읽기 작업 요청 추가 (3)
io_service에 인쇄 처리기 추가 (4)
io_service 실행 (5)
일이나 핸들러가 있습니까?
예, 1 개의 작업과 2 개의 핸들러가 있습니다.
소켓에 데이터가 있습니까? 아니, 아무것도 하지마
인쇄 처리기 실행 (1)
일이나 핸들러가 있습니까?
예, 1 개의 작업과 1 개의 핸들러가 있습니다.
소켓에 데이터가 있습니까? 아니, 아무것도 하지마
인쇄 처리기 실행 (4)
일이나 핸들러가 있습니까?
네, 작품이 1 개 있습니다.
소켓에 데이터가 있습니까? 아니, 계속 기다려
-소켓 데이터 수신-
소켓에 데이터가 있습니다. 버퍼로 읽습니다.
io_service에 handle_async_receive 핸들러 추가
일이나 핸들러가 있습니까?
예, 핸들러가 1 개 있습니다.
handle_async_receive 핸들러 실행 (3)
일이나 핸들러가 있습니까?
아니요, io_service를 중지됨으로 설정하고 반환합니다.
읽기가 완료되면 공지 방법, 그것은 또 다른 추가 핸들러를 받는 io_service
. 이 미묘한 세부 사항은 비동기 프로그래밍의 중요한 기능입니다. 것이 허용 핸들러가 서로 연결되어야한다. 예를 들어, handle_async_receive
예상 한 모든 데이터를 얻지 못한 경우 해당 구현은 다른 비동기 읽기 작업을 게시하여 io_service
더 많은 작업을 수행하여 io_service.run()
.
그 때 메모를 수행 io_service
작업의 부족을 가지고, 응용 프로그램해야 을 실행하기 전에 다시.reset()
io_service
예제 질문 및 예제 3a 코드
이제 질문에서 참조 된 두 가지 코드를 살펴 보겠습니다.
질문 코드
socket->async_receive
에 작업을 추가합니다 io_service
. 따라서 io_service->run()
읽기 작업이 성공 또는 오류로 ClientReceiveEvent
완료되고 실행이 완료되거나 예외가 발생할 때까지 차단됩니다 .
이해하기 쉽게하기 위해 주석이 달린 더 작은 예제 3a가 있습니다.
void CalculateFib(std::size_t n);
int main()
{
boost::asio::io_service io_service;
boost::optional<boost::asio::io_service::work> work =
boost::in_place(boost::ref(io_service));
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service)
);
}
io_service.post(boost::bind(CalculateFib, 3));
io_service.post(boost::bind(CalculateFib, 4));
io_service.post(boost::bind(CalculateFib, 5));
work = boost::none;
worker_threads.join_all();
}
상위 수준에서 프로그램은 io_service
의 이벤트 루프를 처리 할 2 개의 스레드를 생성 합니다 (2). 결과적으로 피보나치 수를 계산하는 간단한 스레드 풀이 생성됩니다 (3).
질문 코드와이 코드의 한 가지 주요 차이점은 실제 작업과 핸들러가 (3)에 추가 되기 전에이 코드가 io_service::run()
(2) 를 호출한다는 것 io_service
입니다. 이 io_service::run()
즉시 반환되는 것을 방지하기 위해 io_service::work
객체가 생성됩니다 (1). 이 개체 io_service
는 작업 부족을 방지합니다 . 따라서 작업 io_service::run()
이 없어도 반환되지 않습니다.
전체 흐름은 다음과 같습니다.
- 에 추가 된
io_service::work
개체를 만들고 추가 합니다 io_service
.
- 을 호출하는 스레드 풀이 생성되었습니다
io_service::run()
. 이러한 작업자 스레드는 개체로 io_service
인해 반환되지 않습니다 io_service::work
.
- 에 피보나치 수를 계산하는 3 개의 핸들러를 추가
io_service
하고 즉시 반환합니다. 주 스레드가 아닌 작업자 스레드가 이러한 처리기를 즉시 실행하기 시작할 수 있습니다.
io_service::work
개체를 삭제 합니다.
- 작업자 스레드 실행이 완료 될 때까지 기다리십시오. 이는 3 개의 핸들러가 모두 실행을 완료 한 후에 만 발생합니다.
io_service
두 핸들러 모두 핸들러도 작동하지 않기 때문입니다.
코드는에 핸들러가 추가 된 io_service
다음 io_service
이벤트 루프가 처리 되는 원본 코드와 동일한 방식으로 다르게 작성 될 수 있습니다 . 이렇게하면을 사용할 필요가 없으며 io_service::work
다음 코드가 생성됩니다.
int main()
{
boost::asio::io_service io_service;
io_service.post(boost::bind(CalculateFib, 3));
io_service.post(boost::bind(CalculateFib, 4));
io_service.post(boost::bind(CalculateFib, 5));
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service)
);
}
worker_threads.join_all();
}
동기 대 비동기
문제의 코드는 비동기 작업을 사용하고 있지만 비동기 작업이 완료 될 때까지 대기하므로 효과적으로 동 기적으로 작동합니다.
socket.async_receive(buffer, handler)
io_service.run();
다음과 같습니다.
boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);
일반적으로 동기 및 비동기 작업을 혼합하지 마십시오. 종종 복잡한 시스템을 복잡한 시스템으로 바꿀 수 있습니다. 이 답변 은 비동기 프로그래밍의 장점을 강조하며 일부는 Boost.Asio 문서 에서도 다룹니다 .