【问题标题】:Passing std::shared_ptr to thread and going out of scope immediately将 std::shared_ptr 传递给线程并立即超出范围
【发布时间】:2020-12-20 17:29:34
【问题描述】:

我正在创建一个小型 C++ 服务器,它应该像这样在无限循环中处理请求。

void Server::start_accept_loop(int server, SSL_CTX* ctx){
    sockaddr_in incoming_addr;
    socklen_t len = sizeof(incoming_addr);
    int client_sock;
    SSL* ssl;
    while(Server::is_accepting){
        client_sock = accept(server, (sockaddr*)&incoming_addr, len);
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client_sock);
        if (SSL_accept(ssl) == -1){
            printf("Unable to accept ssl connection\n");
        }else{
            std::shared_ptr<Client> client (new Client(ssl));
            std::thread th (background_client, client);
        }
    }
}
void Server::background_client(std::shared_ptr<Client> client_ptr){
    std::shared_ptr<Client> client = client_ptr;
    int request = 0;
    request = client->get_request();
    ...
}

其实我有几个问题。

  1. 当创建的客户端 shared_ptr 在线程创建后超出范围时,它会减少引用计数并释放对象。这是新线程的竞争条件,或者是在std::thread th (background_client, client); 行增加了引用计数。基本上,我想知道是否需要在 while 循环超出范围并创建新的 shared_ptr 之前等待一段时间,或者是否需要立即增加引用计数?
  2. 我的第二个问题是关于 SSL 结构指针的。如果底层的 client_sock 和 incoming_addr 对象超出范围而没有在堆上动态创建,那么可能依赖于这些的 SSL 连接是否存在问题,或者 SSL 指针是否包含所有这些信息的符合动态副本?李>
  3. 还有什么我应该知道的吗?

谢谢。

【问题讨论】:

  • 问题 1 和 2 真的不相关,我认为它们应该是单独的帖子。
  • 它们都是范围问题。我认为它们是相关的。我无法使用相同的代码创建 2 个帖子。
  • 好吧,在上面的代码中你创建线程然后立即离开作用域。它会立即抛出异常。如果您不想等到它完成,您应该使用detach()。一般来说,您应该使用更好的多线程框架,而不是仅仅创建线程来管理连接,因为您应该正确安全地退出应用程序。如果不是所有分离的线程都完成了他们的工作,那么这是一个问题。而你想对所有任务和退出但优雅?那该怎么办呢?
  • 是的,我必须分离线程才能工作。我不明白你的其他问题。它是一个服务器,所以它应该一直运行,除了维护等。所以在使用时我不介意线程被分离。

标签: c++ multithreading openssl smart-pointers


【解决方案1】:
  1. 当实例化新的shared_ptr 时(即在调用thread ctor 时),引用计数会增加,所以我无法想象Client 对象会被释放,除非线程被销毁,或者直到Server::background_client返回。

  2. 如果不查看 Client 实现,可能无法判断。我会说可能是那个类的问题,以确保 SSL 上下文保持不变,但是如果不查看其余代码也很难说。

【讨论】:

  • 客户端实现很简单。它只是使用 SSL 上下文并在析构函数中释放它。也许它是不相关的,但我认为我有一些段错误问题,因为incoming_addr 和client_sock 超出了范围。也许没有关系。在动态创建所有 SSL 结构之后。
  • 谢谢。据我了解,线程的ctor在循环中被称为内联,并且引用计数立即增加。
【解决方案2】:

#2 在我看来不错。

incoming_addr 通过指针传递给系统调用accept,它只读取它的数据并且不会存储指针。内核将保留它自己的信息副本,它不需要继续引用您的信息。因此incoming_addr 不需要在调用accept 后保持活动状态。

client_sock 是一个int 并通过值传递给SSL_set_fd(这是一个C API,因此除了作为指针之外不能接受引用,但您也可以通过阅读文档来确认这一点)。所以套接字 fd 必须被复制以存储在SSL 对象中。 SSL_set_fd 中的代码甚至都不知道client_sock 变量的地址,因此它不可能引用它;因此client_sock 在函数调用后不需要保持活动状态。

当然,SSL 对象本身会一直存活到SSL_freed。

【讨论】:

    【解决方案3】:

    当创建的客户端 shared_ptr 在线程创建后超出范围时,它会减少引用计数并释放对象。这是新线程的竞争条件,或者是在 std::thread th (background_client, client) 处增加了引用计数;线。基本上,我想知道是否需要在 while 循环超出范围并创建新的 shared_ptr 之前等待一段时间,或者是否需要立即增加引用计数?

    shared_ptr 超出范围时,引用计数将减少,是的。但是Client 对象不会被销毁,因为shared_ptr 正在被复制到线程中,立即增加引用计数。线程有一个活动引用,所以在线程结束之前对象不会被销毁。

    我的第二个问题是关于 SSL 结构指针。如果底层的 client_sock 和 incoming_addr 对象超出范围而没有在堆上动态创建,那么可能依赖于这些的 SSL 连接是否存在问题,或者 SSL 指针是否包含所有这些信息的符合动态副本?

    您没有为 SSL 结构提供您创建的 sockaddr_in,因此无需担心超出范围。如果 OpenSSL 需要地址信息,它只会从套接字对象中检索自己的副本。至于您的 client_sock 变量,它只是一个整数,它按值复制,因此也不用担心超出范围。 SSL 结构将拥有它自己的描述符副本,获取它的所有权,并且描述符所引用的底层套接字对象将不会被释放,直到当 SSL 结构被@987654327 销毁时,描述符显式close()'d @。

    还有什么我应该知道的吗?

    显示的代码没有检查accept() 是否失败。如果 SSL/TLS 握手失败,它会泄漏套接字对象和 SSL 结构。

    【讨论】:

    • 谢谢。是的,如果连接失败,SSL_accept 会被 tcheked 和 ssl 结构释放。我没有在问题中添加它。接受呼叫也被检查。
    猜你喜欢
    • 2016-06-15
    • 1970-01-01
    • 2019-06-04
    • 2019-10-27
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 2017-03-22
    • 1970-01-01
    相关资源
    最近更新 更多