Boost ASIO:strand保护handler调用async_read_some()仍崩溃,求正确处理方式
你的核心问题是异步操作的发起未通过strand串行化,仅包裹handler不足以保证线程安全。Boost.Asio中,socket的所有操作(包括发起异步操作、关闭等)都必须在同一个执行上下文(这里是strand)中执行,否则会引发内部状态的竞态,导致你看到的空指针解引用错误。
为什么之前的代码会崩溃?
你虽然用bind_executor把handler绑定到了strand,但async_read_some的调用是在第三方库的线程中执行的,而socket的关闭操作(假设在其他线程或strand上下文中)会修改socket的内部状态。当这两个操作并发时,epoll_reactor的内部数据结构可能已经被销毁,导致访问descriptor_data时出现空指针。
strand的作用是保证handler的串行执行,但它不会自动保护异步操作的发起过程。socket的异步发起操作本身会修改内部状态,必须和其他socket操作(如close)串行化。
正确的处理方式
必须将async_read_some的发起也放到strand中执行,使用ba::dispatch(优先在当前线程执行,如果不在strand上下文则入队)或者ba::post(始终入队)来包裹调用。这样所有socket相关操作都通过strand串行化,避免竞态。
修改后的readSome关键部分代码如下:
// 替换原有的m_socket.async_read_some调用 ba::dispatch(m_sessionStrand, [this, buffer, readHandler]() { m_socket.async_read_some(buffer, readHandler); });
完整修改后的readSome函数:
int32_t Session::readSome (unsigned char* pBuffer, uint32_t bufferSizeToRead, boost::system::error_code& errorCode) { if (bufferSizeToRead == 0) return 0; ba::mutable_buffer buffer(pBuffer, bufferSizeToRead); std::size_t result = 0; errorCode.clear(); m_readerPause.exchange(true); auto readHandler = [&result, &errorCode, self=shared_from_this()](boost::system::error_code ec, std::size_t bytesRead) { result = bytesRead; errorCode = ec; std::unique_lock<std::mutex> lock{m_readerMutex}; m_readerPause.exchange(false); m_readerPauseCV.notify_all(); }; // 将异步读取的发起操作放到strand中执行 ba::dispatch(m_sessionStrand, [this, buffer, readHandler=ba::bind_executor(m_sessionStrand, readHandler)]() mutable { m_socket.async_read_some(buffer, std::move(readHandler)); }); { std::unique_lock<std::mutex> lock{m_readerMutex}; m_readerPauseCV.wait(lock, [this]{ return !m_readerPause.load(std::memory_order_acquire); }); } return result; }
额外注意事项
- C++14兼容:代码中使用的lambda捕获、
shared_from_this等特性均符合C++14标准,无需修改。 - 关闭操作的串行化:确保socket的
close操作也通过同一个strand执行,比如:ba::dispatch(m_sessionStrand, [this]() { m_socket.close(); }); - 条件变量的内存序:
m_readerPause的exchange和load使用的内存序是合理的,无需调整,但要确保m_readerMutex正确保护条件变量的等待逻辑。
崩溃原因的具体解释
你遇到的segfault发生在epoll_reactor::start_op中,是因为socket关闭时已经销毁了内部的per_descriptor_data结构,但此时第三方库线程正在发起async_read_some,导致访问已释放的指针。通过strand串行化所有socket操作,就能保证async_read_some的发起和close操作不会并发执行,从根本上避免这个竞态。
内容的提问来源于stack exchange,提问作者sullyt

