boost :: asio :: io_service 실행 메서드가 차단 / 차단 해제 될 때 혼동
Boost.Asio의 전체 초보자이기 때문에 io_service::run()
. 이 방법이 차단 / 차단 해제 될 때 누군가 나에게 설명해 주시면 감사하겠습니다. 문서에는 다음과 같이 나와 있습니다.
run()
기능 블록은 모든 작업이 완료 될 때까지 파견 할 더 이상의 핸들러가있다, 또는이 될 때까지io_service
중지되었습니다.여러 스레드가
run()
함수를 호출하여io_service
핸들러를 실행할 수 있는 스레드 풀을 설정할 수 있습니다. 풀에서 대기중인 모든 스레드는 동등하며io_service
핸들러를 호출하기 위해 그중 하나를 선택할 수 있습니다.
run()
함수 의 정상적인 종료 는io_service
객체가 중지 되었음을 의미합니다 (stopped()
함수가 true를 반환 함). 후속 호출하는run()
,run_one()
,poll()
나poll_one()
에 대한 이전 호출이없는 한 즉시 반환됩니다reset()
.
다음 문장은 무엇을 의미합니까?
[...] 파견 될 더 이상 핸들러 없음 [...]
의 동작을 이해하는 동안 io_service::run()
이 예제 를 보았습니다 (예 3a). 그 안에서 io_service->run()
작업 지시 를 차단하고 기다립니다.
// WorkerThread invines io_service->run()
void WorkerThread(boost::shared_ptr<boost::asio::io_service> io_service);
void CalculateFib(size_t);
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service);
boost::shared_ptr<boost::asio::io_service::work> work(
new boost::asio::io_service::work(*io_service));
// ...
boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}
io_service->post( boost::bind(CalculateFib, 3));
io_service->post( boost::bind(CalculateFib, 4));
io_service->post( boost::bind(CalculateFib, 5));
work.reset();
worker_threads.join_all();
그러나 작업중인 다음 코드에서 클라이언트는 TCP / IP를 사용하여 연결하고 데이터가 비동기 적으로 수신 될 때까지 실행 메서드를 차단합니다.
typedef boost::asio::ip::tcp tcp;
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service);
boost::shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));
// Connect to 127.0.0.1:9100.
tcp::resolver resolver(*io_service);
tcp::resolver::query query("127.0.0.1",
boost::lexical_cast< std::string >(9100));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
socket->connect(endpoint_iterator->endpoint());
// Just blocks here until a message is received.
socket->async_receive(boost::asio::buffer(buf_client, 3000), 0,
ClientReceiveEvent);
io_service->run();
// Write response.
boost::system::error_code ignored_error;
std::cout << "Sending message \n";
boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error);
run()
아래의 두 가지 예에서 그 동작을 설명하는 모든 설명을 주시면 감사하겠습니다.
기초
간단한 예제로 시작하여 관련 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); // 1
socket.connect(endpoint); // 2
socket.async_receive(buffer, &handle_async_receive); // 3
io_service.post(&print); // 4
io_service.run(); // 5
처리기 는 무엇입니까 ?
핸들러는 콜백에 불과하다. 예제 코드에는 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를 중지됨으로 설정하고 반환합니다.
Notice how when the read finished, it added another handler to the io_service
. This subtle detail is an important feature of asynchronous programming. It allows for handlers to be chained together. For instance, if handle_async_receive
did not get all the data it expected, then its implementation could post another asynchronous read operation, resulting in io_service
having more work, and thus not returning from io_service.run()
.
Do note that when the io_service
has ran out of work, the application must reset()
the io_service
before running it again.
Example Question and Example 3a code
Now, lets examine the two pieces of code referenced in the question.
Question Code
socket->async_receive
adds work to the io_service
. Thus, io_service->run()
will block until the read operation completes with success or error, and ClientReceiveEvent
has either finished running or throws an exception.
Example 3a Code
In hopes of making it easier to understand, here is a smaller annotated Example 3a:
void CalculateFib(std::size_t n);
int main()
{
boost::asio::io_service io_service;
boost::optional<boost::asio::io_service::work> work = // '. 1
boost::in_place(boost::ref(io_service)); // .'
boost::thread_group worker_threads; // -.
for(int x = 0; x < 2; ++x) // :
{ // '.
worker_threads.create_thread( // :- 2
boost::bind(&boost::asio::io_service::run, &io_service) // .'
); // :
} // -'
io_service.post(boost::bind(CalculateFib, 3)); // '.
io_service.post(boost::bind(CalculateFib, 4)); // :- 3
io_service.post(boost::bind(CalculateFib, 5)); // .'
work = boost::none; // 4
worker_threads.join_all(); // 5
}
At a high-level, the program will create 2 threads that will process the io_service
's event loop (2). This results in a simple thread pool that will calculate Fibonacci numbers (3).
The one major difference between the Question Code and this code is that this code invokes io_service::run()
(2) before actual work and handlers are added to the io_service
(3). To prevent the io_service::run()
from returning immediately, an io_service::work
object is created (1). This object prevents the io_service
from running out of work; therefore, io_service::run()
will not return as a result of no work.
The overall flow is as follows:
- Create and add the
io_service::work
object added to theio_service
. - Thread pool created that invokes
io_service::run()
. These worker threads will not return fromio_service
because of theio_service::work
object. - Add 3 handlers that calculate Fibonacci numbers to the
io_service
, and return immediately. The worker threads, not the main thread, may start running these handlers immediately. - Delete the
io_service::work
object. - Wait for worker threads to finish running. This will only occur once all 3 handlers have finished execution, as the
io_service
neither has handlers nor work.
The code could be written differently, in the same manner as the Original Code, where handlers are added to the io_service
, and then the io_service
event loop is processed. This removes the need to use io_service::work
, and results in the following code:
int main()
{
boost::asio::io_service io_service;
io_service.post(boost::bind(CalculateFib, 3)); // '.
io_service.post(boost::bind(CalculateFib, 4)); // :- 3
io_service.post(boost::bind(CalculateFib, 5)); // .'
boost::thread_group worker_threads; // -.
for(int x = 0; x < 2; ++x) // :
{ // '.
worker_threads.create_thread( // :- 2
boost::bind(&boost::asio::io_service::run, &io_service) // .'
); // :
} // -'
worker_threads.join_all(); // 5
}
Synchronous vs. Asynchronous
Although the code in the question is using an asynchronous operation, it is effectively functioning synchronously, as it is waiting for the asynchronous operation to complete:
socket.async_receive(buffer, handler)
io_service.run();
is equivalent to:
boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);
As a general rule of thumb, try to avoid mixing synchronous and asynchronous operations. Often times, it can turn a complex system into a complicated system. This answer highlights advantages of asynchronous programming, some of which are also covered in the Boost.Asio documentation.
To simplify how what run
does, think of it as an employee that must process a pile of paper; it takes one sheet, does what the sheet tells, throws the sheet away and takes the next one; when he runs out of sheets, it leaves the office. On each sheet there can be any kind of instruction, even adding a new sheet to the pile. Back to asio: you can give to an io_service
work in two ways, essentially: by using post
on it as in the sample you linked, or by using other objects that internally call post
on the io_service
, like the socket
and its async_*
methods.
'Development Tip' 카테고리의 다른 글
React 컴포넌트에 강제로 다시 마운트하는 방법은 무엇입니까? (0) | 2020.10.08 |
---|---|
Python을 사용하여 RESTful 서비스에서 JSON 데이터를 가져 오려면 어떻게해야합니까? (0) | 2020.10.08 |
Composer가 라이브러리를 다시 설치하도록하는 방법은 무엇입니까? (0) | 2020.10.08 |
내용과 동일한 너비 (0) | 2020.10.08 |
Sequelize.js 쿼리를 삭제 하시겠습니까? (0) | 2020.10.08 |