当我阅读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是否有助于完成数据同步工作?
已更新
在我谷歌并阅读this和this后,asio确实使用内存fence原语来同步线程中的数据,这比解锁更快,直到处理程序执行完成(speed difference on x86) 。事实上,Java volatile关键字是在写入和放大后通过插入内存屏障来实现的。在阅读这个变量之前,要先发生关系。
如果有人可以简单地描述asio记忆栅栏实现或添加我错过或误解的东西,我会接受它。
答案 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
将在其构造函数中执行获取,并在其析构函数中执行释放。