c++17 std::thread join() : 没有这样的过程

c++17 std::thread join() : No such process

不好意思,标题是点击诱饵...没你想的那么容易解决...那才是真正的挑战

我遇到了一个非常奇怪的问题,即 joinable() 线程无法 join()。

我得到的错误是没有这样的过程

这不是典型的初学者两次加入线程的错误... 这是一个复杂的问题,甚至可能是由内存损坏引起的……但我希望我只是遗漏了一些东西,我需要一个全新的外部视图……我已经在这个问题上工作了两天。

我正在为 Linux 和 Windows 编译。

在 Linux 上(使用 gcc 9.1.0)它每次都能完美运行。

在 Windows 上(使用 linux 机器上的 x86_64-w64-mingw32-g++ 9.2.0 和 windows 机器上的 运行 程序)我总是得到错误。

以下是我可以 100% 确定的内容:

最后一点很可能是问题的根源,尽管我真的不明白是怎么回事。

我也知道包含线程指针的object在join()之前并没有被销毁。 如果成功,我唯一删除此指针的地方就在 join() 之后。 parent object 是包裹在 shared_ptr 中的。

指向该线程的指针也永远不会 used/shared 在其他地方。

代码很难简化并在这里共享,因为它是完整网络系统的一部分,它的所有方面都可能是问题的根源。

哦,实际线程已正确执行,并且所有由此产生的网络通信都正常工作,即使线程无法加入也是如此。

这里是重要部分的一个非常简化的版本,并附有解释所发生情况的注释:

// We instantiate a new ListeningServer then call Start(), 
// then we connect a client to it, we transfer some data, 
// then we call Stop() on the ListeningServer and we get the error, but everything worked flawlessly still

typedef std::function<void(std::shared_ptr<ListeningSocket>)> Func;

class ListeningServer {
    ListeningSocket listeningSocket; // The class' Constructor initializes it correctly

    void Start(uint16_t port) {
        listeningSocket.Bind(port);
        listeningSocket.StartListeningThread([this](std::shared_ptr<ListeningSocket> socket) {
            HandleNewConnection(socket);
        });
    }

    void HandleNewConnection(std::shared_ptr<ListeningSocket> socket) {
        // Whatever we are doing here works flawlessly and does not change the outcome of the error
    }

    void Stop() {
        listeningSocket.Disconnect();
    }
};

class ListeningSocket {
    SOCKET socket = INVALID_SOCKET; // Native winsock fd handle for windows or typedefed to int on linux
    std::thread* listeningThread = nullptr;
    std::atomic<bool> listening = false;

    void StartListeningThread(Func&& newSocketCallback) {
        listening = (::listen(socket, SOMAXCONN) >= 0);
        if (!listening) return; // That does not happen, we're still good
        listeningThread = new std::thread([this](std::shared_ptr<ListeningSocket>&& newSocketCallback){
            while (IsListening()) {
                // Here I have Ommited a ::poll call with a 10ms timeout as interval so that the thread does not block, the issue is happening with or without it
                memset(&incomingAddr, 0, sizeof(incomingAddr));
                SOCKET clientSocket = ::accept(socket, (struct sockaddr*)&incomingAddr, &addrLen);
                if (IsListening() && IsValid(clientSocket)) {
                    newSocketCallback(std::make_shared<ClientSocket>(clientSocket, incomingAddr)); // ClientSocket is a wrapper to native SOCKET with addr info and stuff...
                }
            }
            LOG("ListeningThread Finished") // This is correctly logged just before the error
        }, std::forward<Func>(newSocketCallback));
        LOG("Listening with Thread " << listeningThread->get_id()) // This is correctly logged to the same thread id that we want to join() after
    }

    INLINE void Disconnect() {
        listening = false; // will make IsListening() return false
        if (listeningThread) {
            if (listeningThread->joinable()) {
                LOG("*** Socket Before join thread " << listeningThread->get_id()) // Logs the correct thread id
                try {
                    listeningThread->join();
                    delete listeningThread;
                    listeningThread = nullptr;
                    LOG("*** Socket After join thread") // NEVER LOGGED
                } catch(...) {
                    LOG("JOIN ERROR") // it ALWAYS goes here with "No Such Process"
                    SLEEP(100ms) // We need to make sure the thread still finishes in time
                    // The thread finishes in time and all resulting actions work flawlessly
                } 
            }
        }
        #ifdef _WINDOWS
            ::closesocket(socket);
        #else
            ::close(socket);
        #endif
        socket = INVALID_SOCKET;
    }

};

需要注意的一件重要事情是,在程序的其他地方,我直接实例化了一个 ListeningSocket 并使用 lambda 调用 StartListeningThread() 并且在直接调用 Disconnect() 后加入线程不会失败

此外,此代码的一部分编译在动态链接的共享库中。

问题已解决!

似乎仅在 windows 中,无法从共享库中编译的代码创建线程并尝试从主应用程序中编译的代码加入线程。

基本上,joinable() 将 return 为真,但 .join() 或 .detach() 将失败。

我所要做的就是确保线程是从最初编译在同一个文件中的代码创建和连接的。

这是我问问题时寻找的那种提示,因为我知道它比这更复杂,而且简化的最小代码无法重现问题。

windows 中的这种线程约束没有在任何地方记录(据我所知,我搜索过) 所以它不应该是一个约束并且实际上是我正在使用的编译器中的一个错误是非常合理的。