От новичка до гуру: Курсы программирования на CyberDuff

Дочерний процесс заблокирован полным каналом, не может читать в родительском процессе

Я примерно создал следующий код для вызова дочернего процесса:

// pipe meanings
const int READ = 0;
const int WRITE = 1;

int fd[2];
// Create pipes
if (pipe(fd))
  {
    throw ...
  }
p_pid = fork();
if (p_pid == 0) // in the child
  {
    close(fd[READ]);
    if (dup2(fd[WRITE], fileno(stdout)) == -1)
      {
        throw ...
      }
    close(fd[WRITE]);

    // Call exec
    execv(argv[0], const_cast<char*const*>(&argv[0]));
    _exit(-1);
  }
else if (p_pid < 0) // fork has failed
  {
    throw
  }
else // in th parent
  {
      close(fd[WRITE]);
      p_stdout = new std::ifstream(fd[READ]));
  }

Теперь, если подпроцесс не слишком много пишет в стандартный вывод, я могу дождаться его завершения, а затем прочитать стандартный вывод из p_stdout. Если он пишет слишком много, запись блокируется, и родитель ждет ее вечно. Чтобы исправить это, я попытался подождать с WNOHANG в родительском элементе, если он не закончен, прочитать весь доступный вывод из p_stdout с помощью readsome, немного поспать и повторить попытку. К сожалению, readsome никогда ничего не читает:

while (true)
  {
    if (waitid(P_PID, p_pid, &info, WEXITED | WNOHANG) != 0)
      throw ...;
    else if (info.si_pid != 0) // waiting has succeeded
      break;

    char tmp[1024];
    size_t sizeRead;
    sizeRead = p_stdout->readsome(tmp, 1024);
    if (sizeRead > 0)
      s_stdout.write(tmp, sizeRead);
    sleep(1);
  }

Вопрос: Почему это не работает и как это исправить?

edit: Если есть только дочерний процесс, простое использование read вместо readsome, вероятно, сработает, но процесс имеет несколько дочерних процессов и должен реагировать, как только один из них завершается.

08.12.2011

Ответы:


1

Как предложил Сарнольд, вам нужно изменить порядок ваших звонков. Сначала читай, жди последним. Даже если ваш метод сработал, вы можете пропустить последнее чтение. т. е. вы выходите из цикла до того, как прочитаете последний записанный набор байтов.

Проблема может заключаться в том, что ifstream не блокирует. Мне никогда не нравились потоки ввода-вывода, даже в моих проектах на C++, мне всегда нравилась простота функций C stdio (например, FILE*, fprintf и т. д.). Один из способов обойти это — прочитать, читаем ли дескриптор. Вы можете использовать select, чтобы определить, есть ли данные, ожидающие в этом канале. Вам нужно будет выбрать, если вы все равно собираетесь читать от нескольких детей, так что вы можете изучить это сейчас.

Что касается быстрой читаемой функции, попробуйте что-то вроде этого (обратите внимание, что я не пытался это скомпилировать):

bool isreadable(int fd, int timeoutSecs)
{
    struct timeval tv = { timeoutSecs, 0 };
    fd_set readSet;
    FD_ZERO(&readSet);
    return select(fds, &readSet, NULL, NULL, &tv) == 1;
}

Затем в родительском коде сделайте что-то вроде:

while (true) {
    if (isreadable(fd[READ], 1)) {
        // read fd[READ];
        if (bytes <= 0)
            break;
    }
}

wait(pid);
08.12.2011

2

Я бы предложил переписать код так, чтобы он не вызывал waitpid(2) до тех пор, пока после read(2) вызовы канала не возвращают 0 для обозначения конца файла. Как только вы получите ответ о конце файла из ваших вызовов чтения, вы знаете, что дочерний элемент мертв, и вы, наконец, можете waitpid(2) для него.

Другой вариант — еще больше отделить чтение от сбора данных и выполнять вызовы ожидания в обработчике сигналов SIGCHLD асинхронно с операциями чтения.

08.12.2011
  • Извините, я был неточен в вопросе. Мне нужно использовать readsome вместо read, так как процесс имеет несколько дочерних элементов, и использование read будет блокировать, пока не будет доступен вывод. 08.12.2011
  • Что касается части чтения, да, вы правы, я должен, вероятно, сделать это раньше. Прямо сейчас я пытаюсь прочитать остальную часть вывода после того, как узнаю, что дочерний процесс завершился, возможно, мне следует упорядочить это по-другому. Но будет ли это иметь значение? К сожалению, я не понимаю, что вы подразумеваете под обработчиком SIGCHLD. Не могли бы вы дать ссылку на объяснение для меня? 08.12.2011
  • Что касается проблемы с несколькими детьми: похоже, пришло время вырваться из select(2) - идеально подходит именно для этой задачи. Страница руководства select_tut(2) будет гигантской помощью в первые несколько раз. 08.12.2011
  • Хорошо, я прочитаю об этом. Я надеялся, что смогу использовать поток C++, как указано выше, и мне не придется прибегать к функциям C для конвейера. 08.12.2011
  • Возможно, мне следует упорядочить это по-другому - если ваши дочерние программы когда-либо намереваются записать больше PIPE_BUF байт (4096 байт упоминается в моей pipe(7) справочной странице), то они будут останавливаться до тех пор, пока данные в их канале не будут израсходованы. Они не умрут, пока вы не прочитаете достаточно, чтобы они заполнили оставшийся буфер ядра и возобновили запись. 08.12.2011
  • Я мало что знаю о потоковых функциях C++ — я предполагаю, что они должны работать так, как рекламируется, — но поскольку API POSIX — это то, как процессы взаимодействуют с ядром, я упоминаю об этом. Извините за отключение, но я знаю, где я стою с API POSIX. :) 08.12.2011
  • Ну да, это именно та проблема, с которой я сталкиваюсь - в остальном код работает нормально. Но обратите внимание, что я использую waitid с WNOHANG, которые всегда возвращаются немедленно, поэтому порядок не имеет большого значения. Проблема в том, что C++ iostream::readsome почему-то никогда не читает данные, хотя пайп уже содержит 4096 байт. Любые идеи по этому поводу? 08.12.2011
  • Для обработчика сигнала необходимо установить функцию через sigaction(2) для сигнала SIGCHLD. Ядро организует запуск вашей функции всякий раз, когда умирает один из ваших потомков. Вы чрезвычайно ограничены в том, какие операции вы можете выполнять в обработчике — для этого см. signal(7) справочную страницу (или документы OpenGroup). Как правило, вы просто wait(2) в обработчике и отмечаете статус для остальной части вашей программы, чтобы использовать позже. 08.12.2011
  • О, идея: readsome похоже не работает, но вы не проверяете статус ошибки потока. у cplusplus есть руководство по этому вопросу, но, как обычно, cplusplus.com не ни в коем случае не является окончательным, поэтому используйте любую лучшую документацию, которая у вас есть для битов ошибок ios. У меня затуманиваются глаза, так что мне пора спать. Отредактируйте вопрос с результатами проверки ошибок, чтобы он был более заметен для других, кто займет мое место. :) 08.12.2011
  • Очень хорошая идея, спасибо, но это не то. Если я очищаю статус потока перед чтением, он не улучшается. Кроме того, статус потока всегда хороший. 08.12.2011
  • Новые материалы

    5 простых концепций Python, ставших сложными
    #заранее извините 1) Переменные x = 4 y = 5 Переменная в Python — это символическое представление объекта. После присвоения некоторого объекта переменной Python мы приобретаем..

    «Освоение вероятности: изучение совместной, предельной, условной вероятности и теоремы Байеса —…
    Виды вероятности: Совместная вероятность Предельная вероятность Условная вероятность Диаграмма Венна в вероятностях: В “Set Theory” мы создаем диаграмму Венна...

    Основы Spring: Bean-компоненты, контейнер и внедрение зависимостей
    Как лего может помочь нашему пониманию Когда мы начинаем использовать Spring, нам бросают много терминов, и может быть трудно понять, что они все означают. Итак, мы разберем основы и будем..

    Отслеживание состояния с течением времени с дифференцированием снимков
    Время от времени что-то происходит и революционизирует часть моего рабочего процесса разработки. Что-то более забавное вместо типичного утомительного и утомительного процесса разработки. В..

    Я предполагаю, что вы имеете в виду методы обработки категориальных данных.
    Я предполагаю, что вы имеете в виду методы обработки категориальных данных. Пожалуйста, проверьте мой пост Инструментарий специалиста по данным для кодирования категориальных переменных в..

    Игра в прятки с данными
    Игра в прятки с данными Я хотел бы, чтобы вы сделали мне одолжение и ответили на следующие вопросы. Гуглить можно в любое время, здесь никто не забивается. Сколько регионов в Гане? А как..

    «Раскрытие математических рассуждений с помощью Microsoft MathPrompter и моделей больших языков»
    TL;DR: MathPrompter от Microsoft показывает, как использовать математические рассуждения с большими языковыми моделями; 4-этапный процесс для улучшения доверия и рассуждений в математических..