2016-07-29 3 views
13

다음 코드에서는 poll_one()이 호출 될 때 하나의 처리기 만 실행되기를 기대하기 때문에 항상 출력이 1 일 것으로 예상합니다. 그러나 한 번 약 300 번, 출력 실제로 3입니다. 부스트 라이브러리에 대한 내 이해를 기반으로,이 잘못된 것 같습니다. 비 결정적 행동이 버그인지 예상 되는가? 부스트 ASIO 사용io_service :: poll_one 비 결정적 동작

#include <boost/asio.hpp> 

int main() { 
    boost::asio::io_service io; 
    boost::asio::io_service::work io_work(io); 
    boost::asio::io_service::strand strand1(io); 
    boost::asio::io_service::strand strand2(io); 
    int val = 0; 

    strand1.post([&val, &strand2]() { 
    val = 1; 
    strand2.post([&val]() { 
     val = 2; 
    }); 
    boost::asio::spawn(strand2, [&val](boost::asio::yield_context yield) { 
     val = 3; 
    }); 
    }); 

    io.poll_one(); 
    std::cout << "Last executed: " << val << std::endl; 

    return 0; 
} 

1.60.0.6

+0

왜 downvote? 답변 됨 thanks –

+0

물론 완벽하고 최소한으로 검증 가능하며 쉽게 컴파일 할 수 있습니다. 예외는 발생하지 않습니다. –

+0

케이스 수를 3 개에서 2 개로 줄이는 경우 문제가 나타나지 않습니다. –

답변

12

관찰 된 동작이 잘 정의 발생할 것으로 예상하지만, 일이 자주 발생하기를 기대하지 말아야한다.

Asio에는 제한된 스택 구현이 있으며, 가닥에 대한 기본 할당 전략은 해시입니다. 해시 콜리 전이 발생하면 두 가닥은 동일한 구현을 사용합니다. 위의 예에서

#include <cassert> 
#include <boost/asio.hpp> 

int main() 
{ 
    boost::asio::io_service io_service; 
    boost::asio::io_service::strand strand1(io_service); 
    // Have strand2 use the same implementation as strand1. 
    boost::asio::io_service::strand strand2(strand1); 

    int value = 0; 
    auto handler1 = [&value, &strand1, &strand2]() { 
    assert(strand1.running_in_this_thread()); 
    assert(strand2.running_in_this_thread()); 
    value = 1; 

    // handler2 is queued into strand and never invoked. 
    auto handler2 = [&value]() { assert(false); }; 
    strand2.post(handler2); 

    // handler3 is immediately executed. 
    auto handler3 = [&value]() { value = 3; }; 
    strand2.dispatch(handler3); 
    assert(value == 3); 
    }; 

    // Enqueue handler1. 
    strand1.post(handler1); 

    // Run the event processing loop, executing handler1. 
    assert(io_service.poll_one() == 1); 
} 

: 해시 충돌이 발생하면 예는 다음과 demo에 간단하게

  • io_service.poll_one()가 하나의 준비 핸들러를 실행 (handler1)
  • handler2
  • 를 호출되지 않습니다
  • handler3strand2.dispatch() 내에서 즉시 호출됩니다. strand2.dispatch()이 처리기 내에서 호출되므로 strand2.running_in_this_thread() 개 반환 true

이 관찰 된 행동에 기여하는 다양한 세부 다음과 같습니다 io_service의 이벤트 루프를 실행합니다

  • io_service::poll_one()는 및 차단하지 않고, 그것은 가장 하나 준비가 실행됩니다 실행 핸들러. dispatch() 컨텍스트 내에서 즉시 실행되는 핸들러는 io_service에 대기열에 포함되지 않으며 단일 핸들러를 호출하는 데는 poll_one()의 제한을받지 않습니다.strand.dispatch()에 의해 것처럼-

  • boost::asio::spawn(strand, function) 과부하는 stackful 코 루틴 시작 : 발신자에 대한

    • strand.running_in_this_thread() 경우 반환 false 후 코 루틴이 지연 호출에 대한 strand에 게시됩니다
    • 경우 strand.running_in_this_thread()이 호출자에 대해 true을 반환하면 즉시 coroutine이 실행됩니다.
  • 디스크리트 strand 동일한 구현을 사용하는 객체는 여전히 스트랜드의 보장을 유지합니다. 즉, 동시 실행이 발생하지 않고 order of handler invocation이 잘 정의되어 있습니다. 개별 이산 구현을 사용하는 이산물 strand이 있고 복수 스레드가 io_service을 실행 중이면 동시에 실행중인 이산 가닥을 관찰 할 수 있습니다. 그러나 이산 오브젝트 strand이 동일한 구현을 사용하면 복수 스레드가 io_service을 실행 중이더라도 동시성을 관찰하지 못합니다.이 문제는 documented입니다 :

    구현 게시하거나 다른 가닥 오브젝트를 통해 발송 핸들러가 동시에 호출 될 것을 보장하지 않습니다.

  • Asio는 제한된 묶음 구현을 가지고 있습니다. 현재 기본값은 193이며 BOOST_ASIO_STRAND_IMPLEMENTATIONS을 원하는 수로 정의하여 제어 할 수 있습니다. 이 기능은 원하는 개수 BOOST_ASIO_STRAND_IMPLEMENTATIONS 정의하여 구성 스트랜드 구현의 개수를 만든 Boost.Asio 1.48 release notes

    에 유의한다.

    풀 크기를 줄이면 두 개의 개별 가닥이 동일한 구현을 사용할 확률이 높아집니다. 원래 코드로 풀 크기를 1으로 설정하면 strand1strand2은 항상 동일한 구현을 사용하므로 val은 항상 3 (demo)이됩니다.

  • 가닥 구현을 할당하는 기본 전략은 황금 비율 해시를 사용하는 것입니다. 해싱 알고리즘이 사용됨에 따라 충돌 가능성이 있기 때문에 동일한 구현이 여러 개의 개별 객체 strand에 사용됩니다. BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION을 정의하면 할당 전략을 라운드 로빈으로 변경하여 BOOST_ASIO_STRAND_IMPLEMENTATIONS + 1 가닥 할당이 발생할 때까지 충돌이 발생하지 않도록 할 수 있습니다. 이 기능은 Boost.Asio 1.48 릴리스 노트에 기록됩니다 : 해싱이 아닌 라운드 로빈 방식을 사용하는 가닥 구현의 할당을 전환 새로운 BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION 플래그

    지원이 추가되었습니다.

    • strand1strand2 갖고 이산 구현
    • io_service::poll_one() 직접 배치 된 단일 핸들러를 실행을 : 1 원래 코드에서 관찰 될 때 상기 세부 감안

다음 발생 에 strand1

  • 에 게시 된 처리기에3210 개 세트 1
  • 핸들러가 strand2에 게시에 val은 대기 행렬 및 호출 보증 strand의 주문을 게시 한 이전 핸들러 때까지 만들어지지 코 루틴을 방지로,
  • 코 루틴 작성이 지연됩니다 호출되지 않습니다 strand2로 실행했다 :

    는 스트랜드 객체 s 주어 s.post(a) 일어나도 전에 경우 후자의 외부 스트랜드 수행 s.dispatch(b), 다음 asio_handler_invoke(a1, &a1)이 발생하기 전에-asio_handler_invoke(b1, &b1).

    • 해시 충돌
    • io_service::poll_one()가 실행 동일한 기본 가닥 구현을 사용하여 결과 strand1strand2 발생 : 3이 관찰되는 반면에
  • , strand1에 직접 게시 된 단일 처리기
  • 012에 게시 된 처리기 32,825,643,479,세트 1
  • 핸들러가 strand2에 게시에 val는 큐잉 strand2 안전하게 비의 보장을 유지하면서 코 루틴을 실행할 수있는, 3val 설정 코 루틴이 즉시 생성 boost::asio::spawn() 내에서 호출되는
  • 를 호출되지 않습니다 동시 실행 및 처리기 호출 순서
  • +4

    이 답변은 예술 작품입니다. 나는 가장자리 사건의 작은 데모를 아주 좋아했다. +100 AFAIAC – sehe

    +0

    굉장한 답변입니다. 이것은 스트랜드 클래스의 문서에있는 중요한 정보가 될 것입니다. – hifier

    +0

    @sehe 감사합니다! 지난 몇 년 동안 현저한 해답을 제공하려는 헌신은 저에게 엄청난 동기였습니다. 이 대답은 당신 없이는 여기에 없을 것입니다. –

    관련 문제