【发布时间】:2020-02-01 08:13:17
【问题描述】:
对不起,标题是一个点击诱饵......它并不像你想象的那么容易解决......这是一个真正的挑战
我遇到了一个非常奇怪的问题,即 joinable() 的线程无法 join()。
我得到的错误是没有这样的过程。
这不是一个典型的初学者错误加入线程两次... 这是一个复杂的问题,甚至可能是由内存损坏引起的......但我希望我只是遗漏了一些东西,我需要一个全新的外部观点......我已经在这个问题上工作了两天。
我正在为 Linux 和 Windows 进行编译。
在 Linux(使用 gcc 9.1.0)上,它每次都能完美运行。
在 Windows 上(使用我的 linux 机器上的 x86_64-w64-mingw32-g++ 9.2.0 并在我的 windows 机器上运行程序)我总是收到错误消息。
以下是我可以 100% 确认的内容:
- 线程尚未加入。该线程只调用了一次 join(),然后它就崩溃了。
- 线程不是默认构造的(它是一个用 new 分配的原始指针)
- 线程工作正常(其他线程 join() 工作正常)
- 调用 detach() 而不是 join() 会导致同样的错误
- 不调用 join()(而是休眠一秒钟)“修复”了问题
- 父线程(创建问题线程的线程)与调用 join() 的线程相同
- 无论我们是在 Debug (-ggdb -g -O0) 还是 Release (-O3) 中编译都不会改变结果(Linux 总是可以工作,windows 总是失败)
- 错误的线程是通过一个 lambda 函数创建的,该函数是从另一个 lambda 函数完美转发的
最后一点很可能是问题的根源,尽管我真的不知道如何。
我也知道包含线程指针的对象在join()之前没有被销毁。 如果成功,我删除此指针的唯一位置是在 join() 之后。 父对象被包裹在一个 shared_ptr 中。
指向该线程的指针也从未在其他地方使用/共享。
代码很难在此处简化和共享,因为它是完整网络系统的一部分,并且它的各个方面都可能是问题的根源。
哦,实际线程已正确执行,所有产生的网络通信都可以正常工作,即使线程无法连接。
这是一个非常简化的重要部分,其中 cmets 解释了会发生什么:
// 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() 后加入线程并没有失败
此外,此代码的一部分在动态链接的共享库中编译。
【问题讨论】:
-
你需要构造一个合适的minimal reproducible example。
listeningThread = new std::thread([this](std::shared_ptr<ListeningSocket>)>&& newSocketCallback){错字?同样对于std::forward<Func>(newSocketCallback),我不知道Func是什么,但我假设你真正想要的只是std::move(newSocketCallback)。 -
super5,错字只是在简化我的代码(现已修复)中,它不在实际代码中。 Func 是 std::function
)> 的 typedef 一个最小的可重现示例将是大约 2000 行代码...在这个简化的示例中,将其保持在最低限度,以便我们只看看可能导致问题的原因,我省略了很多不会改变结果并且很容易假设的部分(比如发送和接收数据的函数,在 windows 和 linux 中准备套接字的函数等).. . 所以这将是最小的可重复性。 -
如果这真的需要 2kLoC,那就是它需要的。也许你误解了这个minimal reproducible example 的目的,特别是它不需要做任何事情,而是以任何人都可以抓取、编译和运行的方式展示你的问题,而无需任何进一步的工作。没有它,您的问题是题外话。作为这里的新用户,也可以使用tour 并阅读How to Ask。
-
您的代码不完整;特别是,它似乎缺少一个
main()函数和至少一个#include。请edit您的代码,这是您问题的minimal reproducible example(包括任何必要的输入,但最好不需要任何输入),然后我们可以尝试重现并解决它。您还应该阅读How to Ask。 -
最小的可重现示例不会产生错误。不过,我刚刚找到了原因。事实上,我的一半代码被编译成一个动态链接的单独共享库。只需将代码从库中复制粘贴到主应用程序即可解决问题……但我不能接受该解决方案。我将使用一个包含两部分(共享库代码和主应用程序)的实际最小可重现示例进行更新。
标签: c++ windows multithreading c++17 stdthread