BOOST::ASIO多线程下socket关闭导致进程崩溃问题定位及解决

本文分析了一款基于C++及BOOST库开发的商用车车联网系统中出现的问题,主要表现为在特定条件下,不同线程对同一SOCKET操作导致系统崩溃。通过核心转储文件和日志分析,找到了问题所在并提出了解决方案。

背景:

        本系统为我公司开发的一套商用车车联网系统的网关子系统,采用C++基于BOOST库开发,网络开发库采用了BOOST的ASIO库,线程池采用了基于BOOST的ThreadPool库。

        本次问题中涉及两个线程在不同时刻对同一个SOCKET的操作,这两个线程分属不同的类。

现象:

    系统接入实车进行测试后,在某些场景开会在read时发生异常,或直接抛出错误,或连续多次读到长度为0的数据包(甚至进入死循环),这时候,程序会关闭SOCKET连接,但是若在此时,另一线程正好正在对此SOCKET进行写操作,则会导致系统崩溃

定位:

      core dump 文件:

      #1  0x0000000000451b64 in MessageSender::send(HDConstants::TaskDef*, int) ()
      #2  0x0000000000473a0a in ThreadPoolGateway::abort(HDConstants::TaskDef*, SQ::Memory**, bool, int) ()
      #3  0x0000000000470cc3 in ThreadPoolGateway::task(HDConstants::TaskDef*) ()
      #4  0x0000000000442e2e in boost::function0<void>::operator()() const ()
      #5  0x0000000000444aa3 in        

      boost::threadpool::detail::worker_thread<boost::threadpool::detail::pool_core<boost::function0<void>,             boost::threadpool::fifo_scheduler, boost::threadpool::static_size, boost::threadpool::resize_controller, boost::threadpool::wait_for_all_tasks> >::run() ()
      #6  0x00007f41b0ded1d0 in thread_proxy () from ./lib/libboost_thread.so.1.60.0
      #7  0x000000322a2079d1 in start_thread () from /lib64/libpthread.so.0
      #8  0x0000003229ee8b6d in clone () from /lib64/libc.so.6

     日志文件:

      2016-09-05 15:12:13.432408 [0x00004410-0x00007f4584405700] [1]: LocalTCPConnection::handleRead received msg length:0 ipaddr = 117.136.98.68 port = 10189

     2016-09-05 15:12:13.432499 [0x00004410-0x00007f45906d5700] [0]: MessageSender::send enter
     2016-09-05 15:12:13.432505 [0x00004410-0x00007f4584405700] [1]: ipaddr= 117.136.98.68 port= 10189 tcp connection closed. error: End of file

     2016-09-05 15:12:13.432525 [0x00004410-0x00007f45906d5700] [1]: MessageSender::send response msg:7e8001000501150204111601ff01ff000200937e

     从COREDUMP文件中看,系统崩溃前正在执行send操作,结合日志,发现此时另一线程发现读到了长度为0的数据包,进而执行了断开TCP的操作,而此时SEND操作还未被执行完,从而导致系统崩溃。

解决方案:

     查询代码,其断开TCP的处理代码如下:

     if(e || bytesTransferred == 0){
        status = CONNECTIONCLOSED;
        boost::system::error_code ec;
        socket.shutdown(boost::asio::socket_base::shutdown_both, ec);
        try
        {
            LOG_DEBUG<<"ipaddr= "<<addr4<<" port= "<<port<<" tcp connection closed. error: "<<e.message()<<endl;
            socket.close();
        }
        catch(boost::system::error_code& ex)
        {
            LOG_DEBUG<<"ipaddr= "<<addr4<<" port= "<<port<<"catch exception ex:"<<ex.message()<<endl;
        }
        return;
    }

    此处理比较简单直接,直接关闭了TCP的读写操作,然后释放了TCP连接的资源。

     

<think>我们正在处理一个关于在多线程环境下使用boost::asio和sdbusplus的问题。用户要求提供使用指南。根据提供的引用,我们可以总结出以下几点: 1. 引用[1]展示了如何使用boost::asio创建TCP端点(endpoint)和socket。 2. 引用[2]讨论了多线程环境下使用io_context的两种方式:一种是多个线程共享一个io_context,并使用strand来保证处理程序的顺序执行;另一种是在主线程运行io_context,而在其他线程中投递异步操作,此时需要注意线程安全(使用原子操作等)。 3. 引用[3]提到,boost::asio中的对象(如定时器)与特定的io_context关联,因此在创建这些对象时需要传入io_context。 另外,sdbusplus是一个基于sd-bus的C++封装库,用于D-Bus通信。它本身是线程安全的吗?通常,D-Bus库要求连接(connection)在同一时间只能被一个线程使用,或者需要显式地启用多线程支持并处理锁。 结合boost::asio和sdbusplus在多线程环境下的使用,我们可能需要考虑以下方面: - 如何设置boost::asio的io_context在多线程环境下运行? - 如何确保sdbusplus的D-Bus连接在多线程中安全使用? - 如何将boost::asio的事件循环与sdbusplus的事件循环整合(如果需要)? 由于sdbusplus有自己的事件循环(通常基于sd-bus的事件循环),而boost::asio也有自己的事件循环(io_context),我们可能需要在一个线程中同时处理两个事件循环,或者将两个事件循环整合。但更常见的做法是让它们分别运行在不同的线程中,并通过线程安全的方式通信。 然而,用户的问题集中在“多线程使用指南”,因此我们将重点放在如何安全地在多线程中使用这两个库。 ### 多线程使用boost::asio 根据boost::asio的文档,有以下几种多线程模型: 1. **单io_context,多线程运行io_context::run**:多个线程同时调用同一个io_context的run方法。这样,完成处理程序(completion handlers)可能会被并发执行。因此,对于共享资源,需要使用锁(如互斥量)或strand(一种特殊的执行器,用于确保处理程序按顺序执行)来保证线程安全。 ```cpp boost::asio::io_context io_context; // ... 将一些异步操作投递到io_context中 // 创建多个线程运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_context]() { io_context.run(); }); } // 等待所有线程结束 for (auto& t : threads) { t.join(); } ``` 当使用这种方式时,如果多个处理程序访问共享资源,应该使用strand来包装: ```cpp boost::asio::io_context::strand my_strand(io_context); // 然后使用strand的wrap函数来包装处理程序,确保这些处理程序不会并发执行 my_socket.async_read_some(..., my_strand.wrap([](auto...){ ... })); ``` 2. **每个线程一个io_context**:每个线程有自己的io_context,这样每个io_context都是独立运行的。这种模型通常用于将特定的资源绑定到特定的线程(例如,每个连接绑定一个线程)。这种方式避免了不同线程间的资源竞争,但线程间通信需要额外的机制(如队列)。 ### 多线程使用sdbusplus sdbusplus的D-Bus连接(sdbusplus::bus_t)通常不是线程安全的。根据sd-bus的文档,一个总线连接(bus connection)不应该在多个线程中同时使用,除非显式地启用多线程支持并加锁。但是,sdbusplus本身并没有提供内置的多线程支持。因此,推荐的做法是: - 每个线程使用自己的bus_t对象(即每个线程创建自己的D-Bus连接),或者 - 在多个线程间共享一个bus_t对象,但必须确保同一时间只有一个线程在调用该对象的方法(通过将所有的D-Bus操作分派到同一个io_context的strand中执行,或者使用一个专门的线程来处理D-Bus连接)。 然而,创建多个D-Bus连接可能会增加系统负担,并且可能不是所有情况下都允许(例如,某些总线只能有一个连接)。 因此,更常见的做法是使用一个单独的线程来处理D-Bus连接,然后通过消息队列或其他线程间通信机制将请求从其他线程传递给这个专用线程。 ### 结合boost::asio和sdbusplus 如果我们希望在一个应用程序中同时使用boost::asio和sdbusplus,并且希望它们都能在多线程环境下工作,我们可以考虑以下架构: 1. 创建一个io_context对象,用于处理所有的异步I/O(如网络、定时器等)。 2. 创建一个sdbusplus::bus_t对象,用于D-Bus通信。 3. 将sdbusplus::bus_t与boost::asio整合:sdbusplus的bus_t可以获取底层的文件描述符(通过`int get_fd()`),然后我们可以将这个文件描述符注册到boost::asio的io_context中,以便在D-Bus有事件时通知asio,然后我们可以在asio的事件循环中处理D-Bus事件。 具体步骤: - 获取bus_t的文件描述符:`int fd = bus.get_fd();` - 创建一个boost::asio::posix::stream_descriptor对象,将其关联到io_context和这个文件描述符。 - 使用async_wait或async_read_some来监视这个文件描述符的可读事件。当可读时,调用`bus.process()`来处理D-Bus事件(注意:`bus.process()`处理一个事件,然后返回,因此可能需要循环处理直到没有事件)。 - 重复注册异步读取。 这样,我们就可以在boost::asio的事件循环中处理D-Bus事件。 4. 多线程处理:我们可以使用多个线程运行io_context的run方法。但是,由于D-Bus连接(bus_t)不是线程安全的,我们需要确保所有对bus_t的操作(包括`process()`和任何发送消息的方法)都在同一个strand中执行,或者在一个线程中执行(即只在一个线程中运行io_context,而其他线程通过投递任务到该io_context来间接操作bus_t)。 ### 示例代码框架 下面是一个简化的示例,展示如何将sdbusplus::bus_t整合到boost::asio的事件循环中,并在多线程环境下运行(但确保bus_t操作在一个strand中执行): ```cpp #include <boost/asio.hpp> #include <sdbusplus/asio/connection.hpp> #include <sdbusplus/bus.hpp> #include <thread> int main() { boost::asio::io_context io_context; // 创建sdbusplus的bus对象,使用asio的封装:sdbusplus::asio::connection // 注意:sdbusplus::asio::connection已经封装了与asio的整合 auto bus = std::make_shared<sdbusplus::asio::connection>(io_context); // 使用bus对象注册D-Bus对象和方法... // 创建strand,用于保护对bus的操作(实际上,sdbusplus::asio::connection内部可能已经做了保护,但为了安全,我们仍然使用strand) auto strand = std::make_shared<boost::asio::io_context::strand>(io_context); // 在多个线程中运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_context]() { io_context.run(); }); } // 在主线程中,我们可以投递一些操作到strand中,这些操作将安全地访问bus boost::asio::post(*strand, [bus]() { // 在strand中安全地使用bus // 例如:调用D-Bus方法 }); // ... 其他操作 // 等待所有线程结束 for (auto& t : threads) { t.join(); } return 0; } ``` 注意:sdbusplus库中有一个`asio`子目录,其中提供了`connection`类,这个类已经封装了与boost::asio的整合。因此,我们不需要手动注册文件描述符到asio,因为`connection`内部已经处理了。 ### 总结 在多线程环境下使用boost::asio和sdbusplus,关键点在于: - 使用boost::asio的io_context在多线程中运行(多个线程调用run),并使用strand来保证对共享资源(包括sdbusplus的bus对象)的访问是线程安全的。 - 使用`sdbusplus::asio::connection`来创建bus连接,它已经与boost::asio整合,因此可以直接在asio的事件循环中处理D-Bus事件。 - 所有对bus对象的操作(包括调用D-Bus方法、处理信号等)都应该通过同一个strand来投递,以确保这些操作不会并发执行。 这样,我们就可以安全地在多线程应用中使用boost::asio和sdbusplus了。 ### 相关问题 1. 如何将sdbusplus的D-Bus事件循环整合到boost::asio中? 2. 在多线程中使用sdbusplus的bus_t对象有哪些注意事项? 3. 除了使用strand,还有哪些方法可以保证boost::asio处理程序的线程安全?</think>### 多线程环境下使用 Boost.Asio 和 sdbusplus 指南 #### 一、Boost.Asio 多线程核心机制 1. **io_context 多线程模型** - **共享 io_context**:多个线程共同执行 `io_context::run()`,需配合 `strand` 保证处理程序顺序执行 ```cpp boost::asio::io_context io_ctx; boost::asio::io_context::strand my_strand(io_ctx); // 创建strand[^2] // 线程池运行io_context std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io_ctx] { io_ctx.run(); }); } ``` - **独立 io_context**:每个线程拥有专属 `io_context`,避免资源竞争 ```cpp std::vector<std::unique_ptr<boost::asio::io_context>> io_contexts; for (int i = 0; i < 4; ++i) { io_contexts.push_back(std::make_unique<boost::asio::io_context>()); std::thread([ctx=io_contexts.back().get()] { ctx->run(); }).detach(); } ``` 2. **线程安全对象绑定** 需确保异步对象(如 `socket`/`timer`)与特定 `io_context` 关联[^3]: ```cpp boost::asio::steady_timer timer(io_ctx); // 绑定到io_context timer.expires_after(std::chrono::seconds(1)); timer.async_wait([](auto...){ /* 处理程序 */ }); ``` #### 二、sdbusplus 多线程实践 1. **D-Bus 连接管理** - **单连接多线程**:使用 `sdbusplus::asio::connection` 封装,通过 `strand` 串行化访问 ```cpp auto bus = std::make_shared<sdbusplus::asio::connection>(io_ctx); boost::asio::post(my_strand, [bus] { auto method = bus->new_method_call("service", "/path", "interface", "method"); method.append(/* 参数 */); bus->call(method); // 安全调用 }); ``` - **多连接独立线程**:每个线程创建专属 D-Bus 连接 ```cpp void worker_thread() { sdbusplus::asio::connection local_bus(io_ctx_local); // 独立使用local_bus } ``` 2. **信号处理同步** 使用 `asio::signal_set` 绑定到 `strand`: ```cpp boost::asio::signal_set signals(io_ctx, SIGINT, SIGTERM); signals.async_wait(my_strand.wrap([](auto, auto){ /* 安全处理信号 */ })); ``` #### 三、整合架构示例 ```mermaid graph TD A[主线程] -->|创建| B[io_context] A -->|创建| C[sdbusplus::connection] B --> D[线程池 run] C -->|绑定| B D -->|strand 封装| E[DBus 方法调用] D -->|strand 封装| F[定时器操作] D -->|strand 封装| G[信号处理] ``` #### 四、关键注意事项 1. **共享资源保护** - 跨线程数据访问需使用 `std::atomic` 或互斥锁 - DBus 对象路径注册需在同一个线程完成 2. **性能优化** - I/O 密集型任务:共享 `io_context` + `strand` 分组[^2] - 计算密集型任务:独立 `io_context` 隔离负载 3. **错误处理** ```cpp timer.async_wait(my_strand.wrap([](boost::system::error_code ec) { if (ec) { // 统一错误处理路径 } })); ``` > **最佳实践**:对于DBus代理对象,推荐使用 `sdbusplus::asio::proxy` 自动匹配 `io_context` 和 `strand`,简化线程安全管理。 --- ### 相关问题 1. 如何避免 `io_context` 在多线程中的资源竞争问题? 2. `sdbusplus::asio::connection` 在哪些场景下需要手动加锁? 3. 如何设计超时机制整合 Boost.Asio 定时器和 D-Bus 方法调用? 4. 多线程环境下 D-Bus 信号订阅有哪些特殊注意事项? [^1]: 创建socket时需绑定io_context [^2]: 多线程分组需使用strand保证顺序 [^3]: 定时器等对象必须关联特定io_context
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值