2013-06-01 1 views
2

asio 소스 코드를 읽을 때 암시 적 스트랜드가 만들어 지더라도 스레드간에 동기화 된 데이터를 만드는 방법에 대해 궁금합니다.asio 암시 적 스트랜드 및 데이터 동기화

io_service :: 실행

mutex::scoped_lock lock(mutex_); 

    std::size_t n = 0; 
    for (; do_run_one(lock, this_thread, ec); lock.lock()) 
    if (n != (std::numeric_limits<std::size_t>::max)()) 
     ++n; 
    return n; 

io_service :: do_run_one의 do_run_one에서

while (!stopped_) 
    { 
    if (!op_queue_.empty()) 
    { 
     // Prepare to execute first handler from queue. 
     operation* o = op_queue_.front(); 
     op_queue_.pop(); 
     bool more_handlers = (!op_queue_.empty()); 

     if (o == &task_operation_) 
     { 
     task_interrupted_ = more_handlers; 

     if (more_handlers && !one_thread_) 
     { 
      if (!wake_one_idle_thread_and_unlock(lock)) 
      lock.unlock(); 
     } 
     else 
      lock.unlock(); 

     task_cleanup on_exit = { this, &lock, &this_thread }; 
     (void)on_exit; 

     // Run the task. May throw an exception. Only block if the operation 
     // queue is empty and we're not polling, otherwise we want to return 
     // as soon as possible. 
     task_->run(!more_handlers, this_thread.private_op_queue); 
     } 
     else 
     { 
     std::size_t task_result = o->task_result_; 

     if (more_handlers && !one_thread_) 
      wake_one_thread_and_unlock(lock); 
     else 
      lock.unlock(); 

     // Ensure the count of outstanding work is decremented on block exit. 
     work_cleanup on_exit = { this, &lock, &this_thread }; 
     (void)on_exit; 

     // Complete the operation. May throw an exception. Deletes the object. 
     o->complete(*this, ec, task_result); 

     return 1; 
     } 
    } 

를 뮤텍스의 잠금 해제 모든 이전에 실행되는 핸들러 : 다음은 ASIO 코드입니다. 암시 적 스트랜드가있는 경우 핸들러는 동시 실행되지 않지만 문제는 스레드 A가 데이터를 수정하는 핸들러를 실행하고 스레드 B는 스레드 A가 수정 한 데이터를 읽는 다음 핸들러를 실행합니다. 뮤텍스 , 스레드 B가 스레드 A가 만든 데이터의 변경을 어떻게 보았습니까? 핸들러 실행 이전에 뮤텍스 잠금을 해제해도 쓰레드 간의 관계는 액세스 한 핸들러의 데이터에 액세스하지 못합니다. 내가 더 갈 때, 핸들러 실행이 일이라고 fenced_block 사용

completion_handler* h(static_cast<completion_handler*>(base)); 
    ptr p = { boost::addressof(h->handler_), h, h }; 

    BOOST_ASIO_HANDLER_COMPLETION((h)); 

    // Make a copy of the handler so that the memory can be deallocated before 
    // the upcall is made. Even if we're not about to make an upcall, a 
    // sub-object of the handler may be the true owner of the memory associated 
    // with the handler. Consequently, a local copy of the handler is required 
    // to ensure that any owning sub-object remains valid until after we have 
    // deallocated the memory here. 
    Handler handler(BOOST_ASIO_MOVE_CAST(Handler)(h->handler_)); 
    p.h = boost::addressof(handler); 
    p.reset(); 

    // Make the upcall if required. 
    if (owner) 
    { 
     fenced_block b(fenced_block::half); 
     BOOST_ASIO_HANDLER_INVOCATION_BEGIN(()); 
     boost_asio_handler_invoke_helpers::invoke(handler, handler); 
     BOOST_ASIO_HANDLER_INVOCATION_END; 
    } 

이 무엇을? 울타리가 C++ 11에서 지원하는 싱크 프리미티브처럼 보이지만이 펜스는 완전히 asio 자체에 의해 작성됩니다. 이 fenced_block이 데이터 동기화 작업을 수행하는 데 도움이됩니까?

업데이트]

내가 ASIO 실제로 그 핸들러 (speed difference on x86를) 완료 실행까지 잠금을 해제보다 더 빠른 스레드에서 데이터를 동기화 원시 메모리 울타리를 사용 구글과 thisthis을 읽은 후. 사실 자바 휘발성 키워드는 쓰기 전에 쓰기 메모리 장벽에 의해 구현됩니다. & 전에이 변수를 읽어서 happen-before 관계를 만듭니다.

단순히 누군가가 메모리 펜스 구현을 설명하거나 내가 놓쳤거나 오해 한 것을 추가 할 수 있다면 받아 들일 것입니다.

+0

스레드 A 및 B 문제로 이해할 수없는 것을 자세히 설명 할 수 있습니까? 핸들러가 스트랜드 (암시 적 또는 명시 적)를 통해 실행 중일 때 동시성이 발생하지 않으면 스레드 A가 X를 수정하면 스레드 A 이후에 실행되는 스레드 B가 X에 대한 변경을 관찰하지 못하는 이유는 무엇입니까? 뮤텍스는 동시성이없는 경우 아무런 이점도 제공하지 않습니다. –

+0

이것은 가시성 문제이며 Java 메모리 모델에서 명확하게 정의되었습니다. C++에는 그런 것 (잘 정의 된 메모리 모델)이 없지만 이것은 CPU의 특성이기 때문에 쓰레드 캐시 때문에 동일하다고 생각합니다. 스레드 수정 데이터는 고유 한 영역 (CPU 캐시)에 캐시 된 복사본을 수정하지만 주 메모리는 그대로 유지합니다. 동기화가 없다면 스레드 B는 CPU 캐시가 메인 메모리로 플러시되는지 여부에 따라이 업데이트 된 값을 보거나 보지 못할 수도 있습니다. 하지만 C++에서는 가시성 문제라는 것이 없습니다. 변수를 쓰거나 읽을 때마다 잠금을 사용합니다. – jean

답변

2

조작이 사용자 핸들러를 호출하기 전에 Boost.Asio는 memory fence을 사용하여 핸들러 실행의 상호 실행을 강제하지 않고 적절한 메모리 순서 재 지정을 제공합니다. 따라서 스레드 B는 스레드 A 컨텍스트 내에서 발생한 메모리 변경을 관찰합니다.

C++ 03은 다중 스레드 실행과 관련하여 메모리 가시성에 대한 요구 사항을 지정하지 않았습니다. 그러나 C++ 11은 § 1.10 멀티 스레드 실행 및 데이터 레이스뿐만 아니라 원자 연산 및 스레드 지원 라이브러리 섹션에서 이러한 요구 사항을 정의합니다. Boost 및 C++ 11 뮤텍스는 적절한 메모리 재정렬을 수행합니다. 다른 구현의 경우 뮤텍스 라이브러리의 문서를 검사하여 메모리 재정렬이 발생했는지 확인하는 것이 좋습니다.

Boost.Asio 메모리 펜스는 구현 세부 사항이므로 항상 변경 될 수 있습니다. Boost.Asio는 하나의 메모리 장벽 구현 만 포함 된 asio/detail/fenced_block.hpp 내의 일련의 조건부 정의를 통해 아키텍처/컴파일러 관련 구현에서 자체적으로 추상화합니다. 기본 구현은 클래스 내에 포함되어 있으며 fenced_block 별칭은 typedef를 통해 만들어집니다. 하여 메모리 장벽

#elif defined(__GNUC__) && (defined(__hppa) || defined(__hppa__)) 
# include "asio/detail/gcc_hppa_fenced_block.hpp" 
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) 
# include "asio/detail/gcc_x86_fenced_block.hpp" 
#elif ... 

... 

namespace asio { 
namespace detail { 

... 

#elif defined(__GNUC__) && (defined(__hppa) || defined(__hppa__)) 
typedef gcc_hppa_fenced_block fenced_block; 
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) 
typedef gcc_x86_fenced_block fenced_block; 
#elif ... 

... 

} // namespace detail 
} // namespace asio 

구현은 아키텍처와 컴파일러에 따라 다릅니다 :

여기에 관련 발췌 한 것입니다. Boost.Asio에는 asio/detail/*_fenced_blocked.hpp 헤더 파일이 있습니다.예를 들어, win_fenced_block은 볼랜드에 InterlockedExchange을 사용합니다. 그렇지 않으면 메모리 주소와 함께 사용할 때 암시 적 잠금 접두사가있는 xchg 어셈블리 명령어를 사용합니다. gcc_x86_fenced_block의 경우 Boost.Asio는 memory 어셈블리 명령어를 사용합니다.

울타리를 사용해야하는 경우 Boost.Atomic 라이브러리를 고려하십시오. Boost.Atomic은 Boost 1.53에서 소개되었으며 C++ 11 표준에 기반한 thread and signal fences 구현을 제공합니다. Boost.Asio는 Boost.Atomic이 Boost에 추가되기 전에 자체 메모리 펜스 구현을 사용하고 있습니다. 또한 Boost.Asio 펜스는 범위에 따라 범위가 지정됩니다. fenced_block은 생성자에서 획득을 수행하고 소멸자에서 릴리스를 수행합니다.