2013-06-01 3 views
2

Когда я прочитал исходный код asio, мне любопытно, как asio создавал данные, синхронизированные между потоками, даже неявная цепочка была сделана. Это код в 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

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; 
     } 
    } 

в его do_run_one, то разблокировка мьютекса все, прежде чем выполнить обработчик. Если есть неявная цепочка, обработчик не будет выполняться одновременно, но проблема следующая: thread 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; 
    } 

что это? Я знаю, что забор кажется примитивом sync, который поддерживается C++ 11, но этот забор полностью написан самим asio. Помогает ли это fenced_block выполнять работу по синхронизации данных?

ОБНОВЛЕНО

После того как я Google и читать this и this, ASIO действительно использовать забор памяти примитив для синхронизации данных в потоках, то есть более чем быстрее разблокировать до обработчика выполнить полное (speed difference on x86). На самом деле ключевое слово Java volatile реализовано путем вставки барьера памяти после записи & перед тем, как прочитать эту переменную, чтобы произойти до отношения.

Если кто-то может просто описать реализацию забора памяти в asio или добавить что-то, что я пропустил или неправильно понял, я его приму.

+0

Можете ли вы рассказать о том, что вы не понимаете, с проблемой нити A и B? Если параллелизм не возникает, потому что обработчики выполняются через цепочку (неявную или явную), то если поток А изменяет X, почему поток B, который работает после потока A, не замечает изменения в X? Мьютекс не дает преимуществ при отсутствии параллелизма. –

+0

Это проблема видимости, и она четко определена в модели памяти Java. У C++ нет такой вещи (хорошо определенной модели памяти), но я думаю, что она такая же из-за кэша потоков, потому что это характер процессора. Изменения в Thread A могут изменять только копию, которая кэшируется в своей области (кеш процессора), но оставила основную память нетронутой. Без синхронизации поток B может увидеть или не увидеть это обновленное значение, это зависит от того, будет ли кэш-память кэша в основной памяти. Но в C++ нет такой вещи, которая называется проблемой видимости, она просто говорит: всякий раз, когда вы пишете или читаете переменную, используйте блокировку – jean

ответ

2

Перед тем, как операция вызывает обработчик пользователя, Boost.Asio использует memory fence для обеспечения соответствующего переупорядочения памяти без взаимного выполнения выполнения обработчика. Таким образом, поток B будет наблюдать изменения в памяти, которые произошли в контексте потока A.

C++ 03 не указывал требования к видимости памяти в отношении многопоточного исполнения. Тем не менее, C++ 11 определяет эти требования в § 1.10. Многопоточные исполнения и расписания данных, а также разделы Atomic operations и Thread support. Ускорение и C++ 11 мьютексов выполняют соответствующее переупорядочение памяти. Для других реализаций стоит проверить документацию библиотеки mutex, чтобы проверить, что происходит переупорядочение памяти.

Заслонки памяти 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 для Borland; в противном случае он использует инструкцию сборки xchg, которая имеет неявный префикс блокировки при использовании с адресом памяти. Для gcc_x86_fenced_block Boost.Asio использует инструкцию сборки memory.

Если вам нужно использовать забор, рассмотрите библиотеку Boost.Atomic. Представленный в Boost 1.53, Boost.Atomic обеспечивает реализацию thread and signal fences на основе стандарта C++ 11. Boost.Asio использует собственную реализацию заграждений памяти до добавления Boost.Atomic в Boost. Кроме того, заборы Boost.Asio основаны на области. fenced_block выполнит покупку в своем конструкторе и освободит его деструктор.