Изящный выход из многопоточного процесса - PullRequest
0 голосов
/ 22 октября 2010

Я использую многопоточную программу на C (процесс?), Использую семафоры и потоки.Потоки постоянно взаимодействуют, блокируют, просыпаются и печатают подсказки на stdout без какого-либо вмешательства человека.Я хочу иметь возможность выйти из этого процесса (изящно после распечатки сообщения и записи всех потоков, а не через грубый CTRL+C SIGINT), нажав символ клавиатуры, такой как #.

Какие у меня есть варианты получения такого ввода от пользователя?

Какую дополнительную информацию я могу предоставить, которая поможет решить эту проблему?

Редактировать: Все ваши ответы звучат интересно, но мой основной вопрос остается.Как получить пользовательский ввод, когда я не знаю, какой поток выполняется в данный момент?Кроме того, блокировка семафора с использованием sem_wait() прерывается, если сигнализируется с помощью SIGINT, что может привести к тупику.

Ответы [ 5 ]

1 голос
/ 22 октября 2010

Нет разницы в чтении стандартного ввода из потоков, за исключением случаев, когда несколько потоков пытаются прочитать его одновременно. Скорее всего, ваши потоки - это не все вызывающие функции, которые постоянно читают стандартный ввод.

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

Если символ уничтожения - это единственное, что вам нужно, или если он будет использоваться только для отладки, то вам, вероятно, нужно время от времени запрашивать новые данные на стандартном вводе. Вы можете сделать это, настроив стандартный ввод как неблокирующий, и попытаться время от времени читать его. Если чтение возвращает 0 символов, то ни одна клавиша не была нажата. У этого метода есть некоторые проблемы. Я никогда не использовал stdio.h функций на FILE * после того, как установил базовый файловый дескриптор (int) на неблокирующую, но подозреваю, что они могут действовать странно. Вы можете избежать использования функций stdio и использовать read, чтобы избежать этого. Есть одна проблема, о которой я читал один раз, когда флаг блока / неблокирования мог быть изменен другим процессом, если вы разветвляли и исполняли новую программу, которая имела доступ к версии этого дескриптора файла. Я не уверен, если это проблема во всех системах. Неблокирующий режим можно установить или сбросить с помощью вызова fcntl.

Но вы могли бы использовать одну из функций опроса с очень маленьким (0) таймаутом, чтобы посмотреть, готовы ли данные. Системный вызов poll, вероятно, самый простой, но есть и select. Различные операционные системы имеют другие функции опроса.

#include <poll.h>

...
/* return 0 if no data is available on stdin.
        > 0 if there is data ready
        < 0 if there is an error
*/
int poll_stdin(void) {
    struct pollfd pfd = { .fd = 0, .events = POLLIN };

    /* Since we only ask for POLLIN we assume that that was the only thing that
     * the kernel would have put in pfd.revents */
    return = poll(&pfd, 1, 0);
}

Вы можете вызывать эту функцию в одном из ваших потоков до тех пор, пока она не перенастроится на 0, а вы просто продолжите. Когда он возвращает положительное число, вам нужно прочитать символ из stdin, чтобы увидеть, что это было. Обратите внимание, что если вы используете функции stdio в stdin в других местах, на самом деле могут быть другие символы, уже помещенные в буфер перед новым персонажем. poll говорит вам, что в операционной системе есть что-то новое для вас, а не то, что есть у stdio в C.

Если вы регулярно читаете со стандартного ввода в других темах, то все становится беспорядочным. Я предполагаю, что вы этого не делаете (потому что, если вы работаете правильно, вы, вероятно, не задавали бы этот вопрос).

0 голосов
/ 22 октября 2010

SIGINT на самом деле не так сложен в обращении и часто используется для изящного завершения.Вам нужен обработчик сигнала и способ сообщить всем потокам, что пора останавливаться.Один глобальный флаг, который потоки проверяют в своих циклах и в наборах обработчиков сигналов, может подойти.Тот же подход работает для завершения «по команде пользователя», хотя вам нужен способ получить ввод от терминала - либо опросить в выделенном потоке, либо снова настроить терминал для генерации сигнала для вас.

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

0 голосов
/ 22 октября 2010

Я бы сделал так, чтобы сохранить глобальный int "should_die" или что-то другое, чей диапазон равен 0 или 1, а другой глобальный int "умер", который отслеживает количество завершенных потоков. should_die и dead изначально равны нулю. Вам также понадобятся два семафора для обеспечения мьютекса вокруг глобалов.

В определенный момент поток проверяет переменную should_die (конечно, после получения мьютекса). Если он должен умереть, он получает dead_mutex, увеличивает количество умерших, освобождает dead_mutex и умирает.

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

Если основной поток не порождает все потоки сам, небольшая модификация будет иметь «threads_alive" вместо "умер". threads_alive увеличивается, когда поток разветвляется, и уменьшается, когда поток умирает.

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

~ anjruu

0 голосов
/ 22 октября 2010

В общем, у меня есть потоки, ожидающие множество событий, и одно из этих событий - событие завершения.

В главном потоке, когда я запускаю событие завершения, я жду всех вышедших потоков.

0 голосов
/ 22 октября 2010

У вас будет поток, прослушивающий ввод с клавиатуры, а затем он присоединится () к другим потокам при получении # в качестве ввода.

Другой способ - перехватить SIGINT и использовать его для завершения работы вашего приложения.

...