c ++ 17 std :: thread join (): такого процесса нет - PullRequest
0 голосов
/ 03 октября 2019

Извините, название является наживкой ... Это не так легко решить, как вы думаете ... что это настоящий вызов

У меня очень странныйпроблема, когда поток, который является присоединяемым (), не может присоединиться ().

Я получаю ошибку Нет такого процесса .

Это не типичная ошибка новичка для двухкратного присоединения к потокам ... Это сложная проблема и, возможно, даже вызванная повреждением памяти ... Но я надеюсь, что просто что-то упустил, и мне нужно свежеевнешний вид ... Я работаю над этим вопросом два дня.

Я компилирую для 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 ()

Кроме того, часть этого кода скомпилирована в разделяемой библиотеке, которая динамически связана.

1 Ответ

0 голосов
/ 03 октября 2019

Проблема решена!

Казалось бы, только в Windows нельзя создать поток из кода, скомпилированного в разделяемой библиотеке, и попытаться присоединить его из кода, скомпилированного в основном приложении.

По сути, joinable () вернет true, но .join () или .detach () не удастся.

Все, что мне нужно было сделать, это убедиться, что поток создан и присоединен из кода, изначально скомпилированного в тот же файл.

Это был тот тип подсказки, который я искал, когда задавал вопрос, потому что знал, что он сложнее, и упрощенный минимальный код не сможет воспроизвести проблему.

Это ограничение потоков в Windows нигде не задокументировано (насколько я знаю, и я ПОИСКАЛ). Поэтому весьма вероятно, что оно не должно быть ограничением и фактически является ошибкой в ​​используемом мной компиляторе. ,

...