boost::asio 挂起 _endthreadx

Posted

技术标签:

【中文标题】boost::asio 挂起 _endthreadx【英文标题】:boost::asio hangs _endthreadx 【发布时间】:2018-08-29 17:04:27 【问题描述】:
int main()
  boost::asio::io_context io_context;
  Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);

  std::thread thread_server([&]() 
    server.start();
    io_context.run();
  );

  std::thread thread_client([&]() 
    Client &client = Client::create(SOCKET_ADDRESS, SOCKET_PORT);
    client.start();
    done = true; // <-----atomic
  );

  std::thread thread_stop([&]() 
    while (done == false) 
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
    
    server.stop();
  );

  thread_server.join();
  thread_client.join();
  thread_stop.join();

我正在尝试boost::asio,但遇到了我无法解决的问题。当我在 Linux(用 gcc 编译)上运行程序(上面的简单示例)时,一切都很好。当我在 VS2017CE 的 Release 上运行它时也是如此。但是,当我在 Debug(VS2017CE 也是)上运行它时,它会崩溃并出现异常:

无法取消引用字符串迭代器,因为字符串迭代器已失效

在退出thread_stopthread_server(很可能是第二个)时,它会在_endthreadx 上崩溃。那么这是我的问题:

    Release 和 Debug 基本配置之间有什么区别,这可能会影响代码执行并指出我应该在哪里查看。(我知道一些但找不到与此特定问题相关的任何内容。)

    我犯了哪些影响代码执行的错误。

我已经创建了一些类,所以如果需要我会提供更多代码,但是代码基本上可以工作,所以我只从其中的一部分开始。

【问题讨论】:

Visual Studio 调试 C++ 运行时标准库类型或其迭代器的大小可能与发布版本不同。这意味着您不能将 C++ 标准库类型从使用调试标准库编译的内容传递给使用发布标准库编译的内容。此外,该问题可能同样存在于发布模式中,但由于 iterator debug level 是只需设置为在发布模式下默认未检测到错误的值。 【参考方案1】:

显示的代码对字符串没有任何作用。此外,您没有显示 io_contextClient 实例中使用的内容。

正如给定的,一切都是一个巨大的竞争条件,因为客户端工作可能永远不会运行,但是你总是在发布@的(假定的)异步操作后立即设置done = true 987654326@。

(如果Client::start() 实际上是完全同步的,那么这里可能合理的解释是,但这真的会使static Client&amp; Client::create(...) 的整个存在变得非常奇怪和无用?)。

使用线程休眠是一种反模式,在异步代码中更是如此。


无法取消引用字符串迭代器,因为字符串迭代器已失效

这清楚地表明 MSVC 的 Iterator Debugging 正在工作。它只是告诉你你有一个编程错误。

您的错误导致字符串迭代器在不再有效时被使用。我看不到它,但 99% 的情况下,这是由使用缓冲区的异步操作引起的,该缓冲区在异步操作完成之前被破坏(或修改)。简而言之:

void foo() 
    std::string msg = "message";
    boost::asio::async_write(_socket, boost::asio::buffer(msg), /*...*/);

建议代码

从你的代码中简化,并给出一些提示:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>

using boost::asio::ip::tcp;
using boost::system::error_code;

static std::string const SOCKET_ADDRESS = "127.0.0.1";
static unsigned short const SOCKET_PORT = 6767;

bool check(error_code const& ec, char const* message) 
    std::cout << message << " (" << ec.message() << ")\n";
    return !ec;


struct Server 
    boost::asio::io_context& io_;
    Server(boost::asio::io_context& io, std::string host, unsigned short port) : io_(io), host_(host), port_(port) 

    void start() 
        acc_.set_option(tcp::acceptor::reuse_address(true));
        acc_.listen(5);

        accept_loop();
    

    void stop() 
        io_.post([this]  // thread safety
            acc_.cancel();
            acc_.close();
        );
    

  private:
    void accept_loop() 
        acc_.async_accept(sock_, [this](error_code ec) 
            if (check(ec, "accepted")) 
                std::make_shared<Connection>(std::move(sock_))->start();
                accept_loop();
            
        );
    

    struct Connection : std::enable_shared_from_this<Connection> 
        tcp::socket sock_;
        std::string buffer_;

        Connection(tcp::socket&& sock) : sock_(std::move(sock)) 

        ~Connection() 
            error_code ec;
            std::cout << "Disconnected " << sock_.remote_endpoint(ec) << "\n";
        

        void start() 
            auto self = shared_from_this();

            async_read_until(sock_, boost::asio::dynamic_buffer(buffer_), "\n", [self,this](error_code ec, size_t bytes) 
                 if (check(ec, "received request")) 
                     std::cout << "Request: " << std::quoted(buffer_.substr(0, bytes), '\'') << "\n";
                     if (bytes > 0)
                         std::reverse(buffer_.begin(), buffer_.begin() + bytes - 1); // reverse the request for the response

                     async_write(sock_, boost::asio::buffer(buffer_, bytes), [self,this](error_code ec, size_t bytes) 
                          if (check(ec, "response sent")) 
                              buffer_.erase(0, bytes);
                              start(); // handle more requests, if any
                          
                     );
                 
            );
        
    ;

    std::string host_;
    unsigned short port_;

    tcp::acceptor acc_io_, boost::asio::ip::address_v4::from_string(host_), port_;
    tcp::socket sock_io_;
;

struct Client 
    Client(std::string host, std::string port) : host_(host), port_(port) 

    void start()  
        boost::asio::io_context io;

        tcp::socket s(io);
        tcp::resolver r(io);
        connect(s, r.resolve(host_, port_));

        send_request(s, "hello world\n");
        send_request(s, "bye world\n");
    

  private:
    void send_request(tcp::socket& s, std::string const& request) 
        write(s, boost::asio::buffer(request));

        boost::asio::streambuf sb;
        read_until(s, sb, "\n");

        std::cout << "Received server response: '" << &sb << "'\n";
    

    std::string host_;
    std::string port_;
;

int main()
    boost::asio::io_context io_context;
    Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
    server.start();

    std::thread thread_server([&]()  io_context.run(); );

    
        Client client SOCKET_ADDRESS, std::to_string(SOCKET_PORT);
        client.start();
    

    
        Client client SOCKET_ADDRESS, std::to_string(SOCKET_PORT);
        client.start();
    

    server.stop();
    thread_server.join();

打印

accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49778
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49780
accepted (Operation canceled)

注意有一场启动竞赛。根据您的运气,第一个Client 可能会在Server 开始监听之前尝试连接。我假设这不是您最担心的问题,我将把它留给读者作为练习。

【讨论】:

以上是关于boost::asio 挂起 _endthreadx的主要内容,如果未能解决你的问题,请参考以下文章

如何在 boost asio 中设置阻塞套接字的超时时间?

boost asio ssl::stream<tcp::socket> 是不是支持多个挂起的 http::async_write 调用?

Visual Studio 无法识别 boost::asio

boost::asio 与 boost::unique_future

停止 boost::asio 操作的规范方法

boost::asio - 知道何时必须关闭/关闭连接