【发布时间】:2013-06-01 03:06:42
【问题描述】:
当我阅读 asio 源代码时,我很好奇 asio 如何使数据在线程之间同步,甚至是隐式链。这些是asio中的代码:
io_service::run
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中,mutex的解锁都是在execute handler之前。如果存在隐式链,handler 不会并发执行,但问题是:线程 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 是否有助于完成数据同步工作?
更新
在我google并阅读this和this之后,asio确实使用内存栅栏原语来同步线程中的数据,这比解锁更快,直到处理程序执行完成(speed difference on x86)。事实上,Java volatile 关键字是通过在 write 之后插入内存屏障来实现的,然后再读取这个变量来建立happens-before关系。
如果有人可以简单地描述 asio 内存栅栏的实现或添加我遗漏或误解的内容,我会接受。
【问题讨论】:
-
你能详细说明一下你对线程A和B问题的不理解吗?如果因为处理程序通过链(隐式或显式)执行而没有发生并发,那么如果线程 A 修改了 X,为什么在线程 A 之后运行的线程 B 不会观察到对 X 的更改?在没有并发的情况下,互斥锁没有任何好处。
-
这是可见性问题,它在 java 内存模型中明确定义。 C++ 没有这样的东西(定义明确的内存模型),但我认为由于线程缓存,它是相同的,因为这是 CPU 的一个特性。线程 A 修改数据可能只会修改缓存在自己区域(CPU 缓存)中的副本,而不会触及主内存。如果没有同步,线程 B 可能会或可能不会看到这个更新的值,这取决于 cpu 缓存是否刷新到主内存。但是在c++中,没有所谓的可见性问题,它只是说:每当写入或读取变量时,使用锁
标签: thread-safety boost-asio memory-barriers