Извините, название является наживкой ... Это не так легко решить, как вы думаете ... что это настоящий вызов
У меня очень странныйпроблема, когда поток, который является присоединяемым (), не может присоединиться ().
Я получаю ошибку Нет такого процесса .
Это не типичная ошибка новичка для двухкратного присоединения к потокам ... Это сложная проблема и, возможно, даже вызванная повреждением памяти ... Но я надеюсь, что просто что-то упустил, и мне нужно свежеевнешний вид ... Я работаю над этим вопросом два дня.
Я компилирую для Linux и Windows.
В Linux (с использованием gcc 9.1.0) он работает каждый раз безупречно.
В Windows (при использовании x86_64-w64-mingw32-g ++ 9.2.0 с моего Linux-компьютера и запуске программы на моем компьютере с Windows) я всегда получаю сообщение об ошибке.
Вот что я могу подтвердить с 100% уверенностью:
- Нить уже не была присоединена .. Только один вызов join () для этого потока, и он вылетает.
- Поток НЕ создается по умолчанию (и это необработанный указатель, назначенный с новым)
- Потоки работают (Другие потоки объединяются () работают нормально)
- Вызов detach ()вместо join () вызывает ту же ошибку
- Не вызывая, что join () (и вместо этого спит секунду) «исправляет» проблему
- Родительский поток (тот, который создает проблемный поток) совпадает с вызывающим join ()
- Независимо от того, компилируем ли мы в Debug (-ggdb -g -O0) или Release (-O3), результат не меняется (Linux всегда работает, Windows всегда дает сбой)
- Ошибочный поток создается с помощью лямбда-функции, которая идеально перенаправляется из другой лямбда-функции
Эта самая последняя точка вполне может быть источником проблемы, хотя я действительноне вижу как.
Я также знаю, что объект, содержащий указатель потока, не уничтожается до объединения (). Единственное место, где я удаляю этот указатель, это сразу после join (), если успешно. Родительский объект является обернутым в shared_ptr.
Указатель на этот поток также никогда не используется / не используется в других местах.
Код очень трудно упростить и поделиться с ним, поскольку он является частью полной сетевой системы, и все его аспекты могут быть источником проблемы.
О, и фактический поток выполняется правильно, и все результирующие сетевые коммуникации работают должным образом, даже если поток не может быть присоединен.
Вот очень упрощенная версия важных частей с комментариями, объясняющими, что происходит:
// 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 и вызываю StartListeningThread() с лямбда-выражением, которое не может присоединиться к потоку после прямого вызова Disconnect ()
Кроме того, часть этого кода скомпилирована в разделяемой библиотеке, которая динамически связана.