asio隐式链和数据同步

时间:2013-06-01 03:06:43

标签: thread-safety boost-asio memory-barriers

当我阅读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中,互斥锁的解锁都在执行处理程序之前。如果存在隐式链,则处理程序不会并发执行,但问题是:线程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;
    }
这是什么?我知道fence似乎是一个由C ++ 11支持的同步原语,但这个围栏完全是由asio本身写的。这个fenced_block是否有助于完成数据同步工作?

已更新

在我谷歌并阅读thisthis后,asio确实使用内存fence原语来同步线程中的数据,这比解锁更快,直到处理程序执行完成(speed difference on x86) 。事实上,Java volatile关键字是在写入和放大后通过插入内存屏障来实现的。在阅读这个变量之前,要先发生关系。

如果有人可以简单地描述asio记忆栅栏实现或添加我错过或误解的东西,我会接受它。

1 个答案:

答案 0 :(得分: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内的一系列条件定义从架构/编译器特定实现中抽象出来,其中仅包含单个内存屏障实现。底层实现包含在一个类中,通过typedef为其创建fenced_block别名。

以下是相关摘录:

#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.Atomic在Boost 1.53中引入,提供了基于C ++ 11标准的thread and signal fences实现。在将Boost.Atomic添加到Boost之前,Boost.Asio一直在使用自己的内存栅栏实现。此外,Boost.Asio栅栏是基于范围的。 fenced_block将在其构造函数中执行获取,并在其析构函数中执行释放。