boost :: asio :: io_service가 메서드를 차단 / 차단 해제 할 때 혼동 됨


88

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()아래의 두 가지 예에서 그 동작을 설명하는 모든 설명을 주시면 감사하겠습니다.

답변:


235

기초

간단한 예제로 시작하여 관련 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를 중지됨으로 설정하고 반환합니다.

읽기가 완료되면 공지 방법, 그것은 또 다른 추가 핸들러를 받는 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 코드

이해하기 쉽게하기 위해 주석이 달린 더 작은 예제 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
}

상위 수준에서 프로그램은 io_service의 이벤트 루프를 처리 할 2 개의 스레드를 생성 합니다 (2). 결과적으로 피보나치 수를 계산하는 간단한 스레드 풀이 생성됩니다 (3).

질문 코드와이 코드의 한 가지 주요 차이점은 실제 작업과 핸들러가 (3)에 추가 되기 전에이 코드가 io_service::run()(2) 호출한다는 것 io_service입니다. 이 io_service::run()즉시 반환되는 것을 방지하기 위해 io_service::work객체가 생성됩니다 (1). 이 개체 io_service는 작업 부족을 방지합니다 . 따라서 작업 io_service::run()이 없어도 반환되지 않습니다.

전체 흐름은 다음과 같습니다.

  1. 에 추가 된 io_service::work개체를 만들고 추가 합니다 io_service.
  2. 을 호출하는 스레드 풀이 생성되었습니다 io_service::run(). 이러한 작업자 스레드는 개체로 io_service인해 반환되지 않습니다 io_service::work.
  3. 에 피보나치 수를 계산하는 3 개의 핸들러를 추가 io_service하고 즉시 반환합니다. 주 스레드가 아닌 작업자 스레드가 이러한 처리기를 즉시 실행하기 시작할 수 있습니다.
  4. io_service::work개체를 삭제 합니다.
  5. 작업자 스레드 실행이 완료 될 때까지 기다리십시오. 이는 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));              //   :- 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
}

동기 대 비동기

문제의 코드는 비동기 작업을 사용하고 있지만 비동기 작업이 완료 될 때까지 대기하므로 효과적으로 동 기적으로 작동합니다.

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 문서 에서도 다룹니다 .


13
멋진 게시물입니다. 주의가 충분하지 않다고 느끼기 때문에 한 가지만 추가하고 싶습니다. run ()이 반환 된 후 다시 run () 할 수 있기 전에 io_service에서 reset ()을 호출해야합니다. 그렇지 않으면 대기중인 async_ 작업이 있는지 여부에 관계없이 즉시 반환 될 수 있습니다.
DeVadder 2013

버퍼의 출처는 어디입니까? 뭐야?
ruipacheco

나는 아직도 헷갈 린다. 믹싱이 동기화이고 비동기가 권장되지 않는 경우 순수 비동기 모드는 무엇입니까? io_service.run ();없이 코드를 보여주는 예제를 제공 할 수 있습니까?
Splash

@Splash One은 io_service.poll()미해결 작업을 차단하지 않고 이벤트 루프를 처리하는 데 사용할 수 있습니다 . 동기 작업과 비동기 작업을 혼합하지 않도록하는 기본 권장 사항은 불필요한 복잡성을 추가하지 않고 처리기가 완료하는 데 오랜 시간이 걸리는 경우 응답 성이 떨어지는 것을 방지하는 것입니다. 동기 작업이 차단되지 않는 것을 알고있는 경우와 같이 안전한 경우가 있습니다.
Tanner Sansbury 2015

"Boost.Asio 는 현재run() ....을 호출하고있는 스레드 내 에서만 핸들러가 실행된다는 것을 보장합니다. "에서 "현재"란 무엇을 의미 합니까? N 개의 스레드 (를 호출 했음 run()) 가 있다면 어떤 스레드 가 "현재"스레드입니까? 많을 수 있습니까? 또는 당신이 실행이 완료된 스레드 뜻 async_*()(말을 async_read)되어 보장 아니라 핸들러를 호출?
나와 즈

18

작업 방식을 단순화하려면 run종이 더미를 처리해야하는 직원으로 생각하십시오. 그것은 한 장의 시트를 취하고, 시트가 말하는 것을 수행하고, 시트를 버리고 다음 시트를 취합니다. 시트가 떨어지면 사무실을 떠납니다. 각 시트에는 파일에 새 시트를 추가하는 등 모든 종류의 지침이있을 수 있습니다. 돌아 가기 ASIO합니다 : 당신은에 줄 수있는 io_service본질적으로 두 가지 방법으로 작업 : 사용하여 post당신이 링크 된 샘플로 또는 내부 전화를 다른 개체 사용하여 postio_service등, socketasync_*방법을.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.