2016-06-13 4 views
3

.pop()에서 차단하는 자체 큐를 구현하고 있습니다. 또한이 함수는 시간 초과 인 추가 인수를 허용합니다. 그래서 그 순간 나는 다음과 같은 코드를 가지고있다.throwing exception 대 ​​리턴 코드

template <class T> 
class BlockingQueue { 

private: 
    std::queue<T> m_queue; 
    std::mutex m_mutex; 
    std::condition_variable m_condition; 

public: 
    T pop(uint64_t t_millis) { 
     std::unique_lock<std::mutex> lock(m_mutex); 
     auto status = m_condition.wait_for(
      lock, 
      std::chrono::milliseconds(t_millis), 
      [=] { 
       return !m_queue.empty(); 
      } 
     ); 
     if (!status) { 
      throw exceptions::Timeout(); 
     } 
     T next(std::move(m_queue.front())); 
     m_queue.pop(); 
     return next; 
    }; 
} 

exceptions::Timeout은 나의 예외 다. 이제는 성능 측면에서 던져지는이 예외에 대해 생각 해왔다. 이 함수에서 어떤 종류의 반환 코드를 반환하는 것이 더 좋을까요? 성능에 어떤 영향을 줍니까?

또한 .pop은 이미 리턴 코드를 어떻게 구현합니까? 나는 T과 리턴 코드를 모두 가지고있는 새로운 구조가 필요할 것이라고 생각한다. 복잡성의 증가가 그만한 가치가 있습니까?

+0

예외적 인 경우가 있습니까? 그렇다면 예외를 throw하십시오. 그렇지 않다면, 다르게 구현하십시오. –

+1

[FYI]'pop'에서 대기열 항목을 반환하고 싶지 않을 수도 있습니다 : http://stackoverflow.com/questions/25035691/why-doesnt-stdqueuepop-return-value – NathanOliver

+1

값을 반환하는'pop()' *와 *는 예외를 던집니까? 좋아, 그렇다. –

답변

4

예상치를 충족시키지 못했을 때 예외를 던지려면 상태를 쿼리 할 때 상태 코드를 반환하십시오.

/// pops an object from the stack 
/// @returns an object of type T 
/// @pre there is an object on the stack 
/// @exception std::logic_error if precondition not met 
T pop(); 

/// queries how many objects are on the stack 
/// @returns a count of objects on the stack 
std::size_t object_count() const; 

/// Queries the thing for the last transport error 
/// @returns the most recent error or an empty error_code 
std::error_code last_error() const; 

다음 실행기 기반의 미래와 결합 ASIO 스타일의 반응 경로있다 :이 같은

/// Asynchronously wait for an event to be available on the stack. 
/// The handler will be called exactly once. 
/// to cancel the wait, call the cancel() method 
/// @param handler is the handler to call either on error or when 
///  an item is available 
/// @note Handler has the call signature void(const error_code&, T) 
/// 
template<class Handler> 
auto async_pop(Handler handler); 

를 호출 할 수 있습니다 :

queue.async_pop(asio::use_future).then([](auto& f) { 
    try { 
    auto thing = f.get(); 
    // use the thing we just popped 
    } 
    catch(const system_error& e) { 
    // e.code() indicates why the pop failed 
    } 
}); 
+2

이것이 단일 소비자 대기열 인 경우 'logic_error'는 비어있을 때이를 호출하는 데 적합합니다. 그러나 다중 사용자 스레드 안전 대기열 인 경우 호출자는 대기열이 비어 있지 않음 (TOCTOU)을 보장 할 수있는 방법이 없습니다.이 경우 대기열이 다른 것을 던져야합니다. –

+0

제안 된 API에 아직 시간 초과 대기 기능이 필요합니다. 주어진 것을 감안할 때'void wait_non_empty (uint64_t t_millis); 또는'std :: size_t wait_non_empty (uint64_t t_millis);가'object_count()'를 반환 할 것을 제안합니다. 아니면 위험하게 살고 그냥'wait_non_empty' 대신'wait'라고 부르세요. 다행히 대기열에서 어떤 조건을 기다리는 지 분명합니다. –

+0

@SteveJessop 또는 asio 원자로 경로로 이동하십시오. '템플릿 자동 async_pop (핸들러 && 핸들러); ' –

0
예를 들어

여기에서는 예외가 필요하지 않습니다. "제한 시간"은 예상대로 대기열에서 항목을 가져 오는 것과 같습니다. 시간 초과가 없으면 프로그램이 본질적으로 중단 문제와 동일합니다. 클라이언트가 무한 타임 아웃을 원한다고 가정 해 봅시다. 예외는 이제까지 던질 것입니까? 어떻게 당신이 (당신은 여전히이 게시물 종말 시나리오에서 살아있어 가정?) 같은 예외를 처리 할

을 대신 나는이 두 가지 디자인 선택 이상의 논리 발견 (그들은 유일한 사람 아니에요하지만) :

  • 항목을 사용할 수있을 때까지 차단하십시오. 시간이 초과되면 false을 폴링하고 반환하는 wait이라는 함수를 만들거나 항목을 사용할 수있는 경우 true을 반환합니다. pop() 함수의 나머지 부분은 변경되지 않습니다.

  • 차단하지 마십시오.

    • 작업이 차단한다면 큐가 비어있는 경우, 반환 "바쁜"
    • , 그렇지 않으면
    • 을 "빈"반환 할 수 있습니다 "팝업"과 "성공"
    • 을 반환 : 대신 상태를 반환 당신이 뮤텍스를 가지고 있기 때문에

이러한 옵션은 즉, 비를 기다리는 기능 바람직 보인다.

+0

"클라이언트가 무한 타임 아웃을 원한다고 가정 해 봅시다. - 과장하지 말고, 한계는 5 억 4 천 4 백만년으로 보입니다. –

+0

@SteveJessop 엔터프라이즈 급이라고 들립니다. 나는 대부분의 사람들이 5 분 후에 포기할 것이라고 생각한다. –

+0

'아이템이 사용 가능할 때까지 차단하기 '기본적으로 호출자를 영원히 차단할 것입니다. 방해해서는 안됩니다. 이것을 고려해보십시오 : 저는 큐에서 요소를 팝하는 작업자가 있습니다. 하지만 다른 스레드가 해당 작업자를 비활성화 할 수 있습니다. 그래서 나는 is_running 플래그를 통해 그것을한다.이 플래그는 매번 그리고 매번 점검되어야한다. 이것은 '타임 아웃 (timeout)'이 작용한다는 것입니다. 만약 내가 틀렸다고해도 나에게 맞는 것이지만 여러 소비자의 경우 TOCTOU 때문에 두 가지 해결책이 모두 실패하지는 않습니까? – freakish

0

또한 .pop가 이미 무언가를 반환하기 때문에 추가 반환 코드를 어떻게 구현하겠습니까? 나는 T와 반환 코드를 모두 가지고있는 새로운 구조가 필요할 것이라고 생각한다. 이 필요할 것이다.

BlockingQueue과 함께 사용할 수있는 유형에 대한 추가 요구 사항이 있습니다. 기본 구성 가능해야합니다. pop()std::unique_ptr을 통해 결과를 반환하면 (nullptr으로 제한 시간을 알리는) 결과가 눈에 띄게 오버 헤드가 발생할 수 있으므로 피할 수 있습니다.

여기에 예외 사용의 단점이 없습니다. 시간 제한을 밀리 초 단위로 측정하는 경우 시간 초과가 발생할 경우 예외 처리는 무시할 수 있어야합니다.

+0

'bool'을 반환하기 위해 예외를 던지기와 비교하십시오. 둘 다 개별적으로 무시할 만하지만 차이가 큰 숫자로 표시됩니다. –

+0

그러나 "높은"숫자는 제한 시간에서 오는 엄청난 숫자와 비교할 수 없습니다. – Leon

+0

@Leon : 잠금을 사용하는 코드의 가장 좋은 경우는 경합/차단입니다.이 경우 시간 제한이 설정되는 시간은 중요하지 않으며 실제로 0이며 0과 비교하여 큰 값입니다.최악의 경우는 자물쇠가 많이 쟁점이되고 응용 프로그램이 CPU에 바인딩되어있는 경우 저장 한 내용이 도움이 될 것입니다. 나는 거의 모든 C++ 코드가 예외 비용에 대해 걱정할 필요가 없다는 것에 동의하지만 타임 아웃과 비교하여 왜 그런지 파악하지 못한다 : 앱이하는 다른 처리와 비교할 때 값이 싸기 때문이다. –

0

이런 상황에서 예외를 throw하지 않고 오류를 알리는 방법 중 하나는 Andrei Alexandrescu의 expected<T> 템플릿과 같은 것을 사용하는 것입니다.

그는 약 nice talk을주었습니다. 생각은 expected<T>T이 포함되어 있거나 T을 생성 할 수없는 이유를 설명하는 예외/오류 코드 개체가 포함되어 있습니다.

자신의 구현을 사용하거나 자신의 목적에 맞게 쉽게 아이디어를 적용 할 수 있습니다. 예를 들어, boost::variant<T, error_code> 위의 클래스를 쉽게 만들 수 있습니다.

이것은 C 스타일 정수 오류 코드 및 C++ 예외와는 다른 오류 처리 스타일입니다. 변형 유형을 사용한다고해서 추가 동적 할당을 의미하는 것은 아닙니다. 이러한 코드는 효율적 일 수 있으며 복잡성을 크게 증가시키지 않습니다.

실제로 이것은 오류 처리가 Rust에서 관용적으로 어떻게 수행되는지에 매우 가깝습니다. c.f. 23