Как надежно завершить поток, заблокированный в задаче ввода-вывода

#c

Вопрос:

У меня есть класс, который выполняет поток для постоянного чтения строк из данного потока, которые затем анализируются внутри. В какой-то момент я хочу, чтобы это закончилось, но так getline() как вызов блокируется, он может ждать вечно join() .

 #pragma once

#include <thread>
#include <iostream>

class Parser {
private:
    std::istreamamp; input;
    std::thread parserThread;

public:
    Parser(std::istreamamp; input_) : input(input_)/* ... */{}
    
    ~Parser() {
        stop();
    }
    
    void start() {
        // Avoid multiple threads...
        parserThread = std::thread(amp;Parser::monitorThread, this);
    }
    
    
    void stop() {
        continueParsing = false;    
        parserThread.join(); // Wait for it to finish
        continueParsing = true; // Allow to start another thread at a later point
    }
    
    
private:
    void monitorThread() {
        std::string buffer;
    
        // Constantly reads new input until it's told to stop
        while(std::getline(input, buffer) amp;amp; continueParsing) { 
            //...
        }

    }
};
 

Существует ли какой-либо стандартный способ сделать это? Или мой подход (чтобы поток читался вечно) неверен? Если бы это был C, я бы просто убил нить…

Комментарии:

1. Ничего, о чем я знаю в стандартном C . Возможно, вы захотите оформить заказ на повышение. Асио . Уничтожение потока на большинстве платформ приведет к неопределенному поведению.

2. Моя непосредственная мысль заключается в том, почему вы вытаскиваете ковер из-под процесса, который передает вам данные? Может ли быть никакого сотрудничества между этими двумя?

3. @Galik Это довольно большое приложение на Python, которое постоянно выводит строки json с маркерами распознавания голоса, поэтому я подумал о том, чтобы просто передать его вывод в свою программу на C .

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

5. Может быть, использовать что-то, что обрабатывает символ за символом и добавляет к buffer ? Если установлен флаг отменено, то немедленно выйдите из цикла. Кроме того, ваш код небезопасен для потоков.. Вам нужно использовать atomic , mutex , и condition_variable , чтобы подать потоку сигнал о том, что он должен прекратить обработку, а затем присоединиться к нему. Еще одна проблема-звонить start дважды.. деструктор вашего предыдущего потока будет вызван, и он выдаст исключение, так как он не присоединен или отсоединен.

Ответ №1:

Если std::getline обнаружится конец файла, то он немедленно вернется и прекратит блокировку. Поэтому, если бы вы могли каким-то образом организовать, чтобы это произошло, когда вы хотите, чтобы поток вышел, то это, вероятно, было бы лучшим решением. Однако, если это невозможно, то я боюсь, что ISO C сам по себе не предлагает никакого способа решения проблемы.

Но большинство платформ предлагают расширения для конкретной платформы, которые позволяют одновременно обслуживать более одного объекта ядра. Например, Linux предлагает poll и epoll который позволяет вам ждать ввода в файловый дескриптор и ждать объекта события в одном и том же вызове функции (на самом деле Linux рассматривает объекты событий также как файловые дескрипторы). Microsoft Windows предлагает аналогичную функциональность с WaitForMultipleObjects .

Вы можете создать объект события (используя eventfd в Linux, CreateEvent в Windows) и установить для этого объекта события сигнал, когда вы хотите, чтобы ваш поток отменил ожидание и вышел. Если поток ожидает либо сигнала объекта события, либо нового ввода в файловый дескриптор, то он прекратит ожидание, как только событие станет сигнализированным. Таким образом, у вас больше не будет проблемы с блокировкой потока во время ожидания новых входных данных в файловом дескрипторе.

Если вы хотите реализовать это решение и продолжить использовать std::istream его для ввода, то вам может потребоваться рассмотреть возможность создания собственного std::streambuf класса, который реализует функцию-член underflow таким образом, чтобы он сначала вызывал одну из функций, специфичных для платформы poll / epoll / WaitForMultipleObjects , чтобы дождаться либо появления новых входных данных, либо сигнала о завершении события. Если сигнализируется событие quit, то функция underflow должна вернуться Traits::eof() , что приведет eofbit std::istream к установке и std::getline немедленному возвращению in. В противном случае, как только появится сообщение о наличии новых входных данных, вы можете вызвать одну из функций, специфичных для платформы read / ReadFile чтобы заполнить область получения std::streambuf объекта, при необходимости отрегулируйте указатели объекта.

Комментарии:

1. Большое спасибо! Просто чтобы добавить для дальнейшего использования, я также нашел действительно более уродливое решение, сделав неблокирующее std::getline со статическими буферами и readsome . Я тоже это проверю, это звучит как идеальная летняя тренировка!