У меня есть поток, работающий в фоновом режиме, который читает события с устройства ввода в режиме блокировки, теперь, когда я выхожу из приложения, я хочу правильно очистить поток, но я не могу просто запустить pthread_join (), потому что поток никогда не выйдет из-за блокировки ввода-вывода.
Как мне правильно разрешить эту ситуацию? Должен ли я послать pthread_kill (theard, SIGIO) или pthread_kill (theard, SIGALRM), чтобы сломать блок? Это вообще правильный сигнал? Или есть другой способ решить эту ситуацию и позволить этому дочернему потоку выйти из блокирующего чтения?
В настоящее время немного озадачен, так как ни один из моих поисковых запросов не нашел решения.
Это в Linux и с использованием pthreads.
Обновлено: я немного поигрался с SIGIO и SIGALRM, когда я не устанавливаю обработчик сигнала, они нарушают блокирующий ввод-вывод, но выдают сообщение на консоли («Возможен ввод-вывод»), но когда я устанавливаю обработчик сигнала , чтобы избежать этого сообщения, они больше не нарушают блокирующий ввод-вывод, поэтому поток не завершается. Так что я как бы вернулся к первому шагу.
Пока нить остается заблокированной, она не причинит вреда. Проблема в том, что поток просыпается, когда вы закрываете его. Таким образом, исправление состоит в том, чтобы поместить некоторый код после строки, которая блокирует, что поток не может делать что-либо еще, если завершение работы выполняется.
Там же обсуждается похожая проблема и возможные решения: Дескрипторы файлов и многопоточные программы
Статья дала мне именно то, что я искал - shutdown (fd, SHUT_RDWR) ;. Спасибо.





Я думаю, как вы сказали, единственный способ - это послать сигнал, а затем уловить его и обработать соответствующим образом. Альтернативными вариантами могут быть SIGTERM, SIGUSR1, SIGQUIT, SIGHUP, SIGINT и т. д.
Вы также можете использовать select () в своем дескрипторе ввода, чтобы читать только тогда, когда он готов. Вы можете использовать select () с таймаутом, скажем, в одну секунду, а затем проверить, должен ли этот поток завершиться.
Одно из решений, которое пришло мне в голову в последний раз, когда у меня возникла такая проблема, заключалось в создании файла (например, канала), который существовал только с целью разбудить блокирующие потоки.
Идея состоит в том, чтобы создать файл из основного цикла (или по одному на поток, как предполагает тайм-аут - это даст вам более точный контроль над тем, какие потоки просыпаются). Все потоки, которые блокируют файловый ввод-вывод, будут выполнять select (), используя файл (ы), с которыми они пытаются работать, а также файл, созданный основным циклом (как член чтения набор дескрипторов файла). Это должно вернуть все вызовы select ().
Код для обработки этого «события» из основного цикла нужно будет добавить в каждый из потоков.
Если главному циклу нужно было разбудить все потоки, он мог либо записать в файл, либо закрыть его.
Я не могу сказать наверняка, работает ли это, поскольку реструктуризация означала, что необходимость попробовать это отпала.
Ваш select() может иметь тайм-аут, даже если он случается нечасто, для корректного выхода из потока при определенных условиях. Я знаю, опрос - отстой ...
Другой альтернативой является создание канала для каждого дочернего элемента и добавление его в список файловых дескрипторов, которые отслеживаются потоком. Отправьте байт в канал от родителя, когда вы хотите, чтобы этот дочерний элемент вышел. Никаких опросов ценой трубы за резьбу.
или у вас может быть один канал для всех потоков, статус «готов» возвращается из выбора / опроса нескольким потокам, ожидающим одного дескриптора файла (пока он запущен на уровне). Таким образом, все потоки, ожидающие одного «убийственного» канала, получат уведомление о смерти.
Зависит от того, как ждёт IO.
Если поток находится в состоянии «Бесперебойный ввод-вывод» (обозначен буквой «D» вверху), то вы действительно ничего не можете с этим поделать. Обычно потоки входят в это состояние только на короткое время, делая что-то вроде ожидания, пока страница будет заменена (или загружена по запросу, например, из файла mmap'd или общей библиотеки и т. д.), Однако сбой (особенно сервера NFS) может вызвать чтобы оставаться в этом состоянии дольше.
На самом деле нет способа выбраться из этого состояния "D". Поток не будет отвечать на сигналы (их можно отправлять, но они будут поставлены в очередь).
Если это обычная функция ввода-вывода, такая как read (), write (), или функция ожидания, такая как select () или poll (), сигналы будут доставляться нормально.
Я всегда добавляю функцию «убийство», связанную с функцией потока, которую я запускаю перед соединением, которая гарантирует, что поток будет присоединен в разумные сроки. Когда поток использует блокирующий ввод-вывод, я пытаюсь использовать систему, чтобы снять блокировку. Например, при использовании сокета я бы вызвал на нем kill call отключение (2) или закрыть (2), что заставило бы сетевой стек полностью завершить его.
Реализация сокета в Linux является потокобезопасной.
Сигналы и потоки - тонкая проблема в Linux, судя по различным страницам руководства. Вы используете LinuxThreads или NPTL (если у вас Linux)?
Я не уверен в этом, но я думаю, что обработчик сигнала влияет на весь процесс, поэтому либо вы завершаете весь процесс, либо все продолжается.
Вы должны использовать выбор или опрос по времени и установить глобальный флаг для завершения вашего потока.
Я бы тоже рекомендовал использовать select или другие средства, не связанные с сигналом, для завершения вашего потока. Одна из причин, по которой у нас есть темы, - это попытка уйти от безумия сигналов. Тем не менее ...
Обычно используется pthread_kill () с SIGUSR1 или SIGUSR2 для отправки сигнала потоку. Другие предлагаемые сигналы - SIGTERM, SIGINT, SIGKILL - имеют семантику всего процесса, которая может вас не интересовать.
Что касается поведения, когда вы отправили сигнал, я предполагаю, что это связано с тем, как вы обработали сигнал. Если у вас не установлен обработчик, применяется действие по умолчанию для этого сигнала, но в контексте потока, получившего сигнал. Так, например, SIGALRM будет "обрабатываться" вашим потоком, но обработка будет заключаться в завершении процесса - вероятно, не в желаемом поведении.
Получение сигнала потоком обычно прерывает его чтение с помощью EINTR, если только он действительно не находится в том непрерывном состоянии, как упоминалось в предыдущем ответе. Но я думаю, что это не так, иначе ваши эксперименты с SIGALRM и SIGIO не остановили бы процесс.
Возможно, ваше чтение зациклено? Если чтение завершается возвратом -1, выйдите из этого цикла и выйдите из потока.
Вы можете поиграть с этим очень небрежным кодом, который я собрал, чтобы проверить свои предположения - сейчас я нахожусь в паре часовых поясов от моих книг по POSIX ...
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
int global_gotsig = 0;
void *gotsig(int sig, siginfo_t *info, void *ucontext)
{
global_gotsig++;
return NULL;
}
void *reader(void *arg)
{
char buf[32];
int i;
int hdlsig = (int)arg;
struct sigaction sa;
sa.sa_handler = NULL;
sa.sa_sigaction = gotsig;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
if (sigaction(hdlsig, &sa, NULL) < 0) {
perror("sigaction");
return (void *)-1;
}
i = read(fileno(stdin), buf, 32);
if (i < 0) {
perror("read");
} else {
printf("Read %d bytes\n", i);
}
return (void *)i;
}
main(int argc, char **argv)
{
pthread_t tid1;
void *ret;
int i;
int sig = SIGUSR1;
if (argc == 2) sig = atoi(argv[1]);
printf("Using sig %d\n", sig);
if (pthread_create(&tid1, NULL, reader, (void *)sig)) {
perror("pthread_create");
exit(1);
}
sleep(5);
printf("killing thread\n");
pthread_kill(tid1, sig);
i = pthread_join(tid1, &ret);
if (i < 0)
perror("pthread_join");
else
printf("thread returned %ld\n", (long)ret);
printf("Got sig? %d\n", global_gotsig);
}
Вы правы, read () на самом деле находится в цикле while, который проверяет EINTR, поскольку он находится в сторонней библиотеке, а не в моем собственном коде, я полностью пропустил этот факт, и поэтому простой сигнал не делаю то, что ожидал.
Можно ли с помощью этого метода освободить ресурсы, полученные с помощью flockfile?
Я думаю, что самый чистый подход - это поток, использующий условные переменные в цикле для продолжения.
Когда запускается событие ввода-вывода, должно быть указано условие.
Основной поток может просто сигнализировать об условии, изменяя предикат цикла на false.
что-то типа:
while (!_finished)
{
pthread_cond_wait(&cond);
handleio();
}
cleanup();
Помните, что условные переменные правильно обрабатывают сигналы. У них могут быть такие вещи, как «ложное пробуждение». Поэтому я бы обернул вашу собственную функцию вокруг функции cond_wait.
struct pollfd pfd;
pfd.fd = socket;
pfd.events = POLLIN | POLLHUP | POLLERR;
pthread_lock(&lock);
while(thread_alive)
{
int ret = poll(&pfd, 1, 100);
if (ret == 1)
{
//handle IO
}
else
{
pthread_cond_timedwait(&lock, &cond, 100);
}
}
pthread_unlock(&lock);
thread_alive - это переменная, зависящая от потока, которая может использоваться в сочетании с сигналом для завершения потока.
Что касается раздела ввода-вывода дескриптора, вам необходимо убедиться, что вы использовали open с параметром O_NOBLOCK, или, если это сокет, есть аналогичный флаг, вы можете установить MSG_NOWAIT ??. для других fds я не уверен
Я удивлен, что никто не предложил pthread_cancel. Недавно я написал программу многопоточного ввода-вывода, и впоследствии вызовы cancel () и join () отлично работали.
Первоначально я пробовал использовать pthread_kill (), но в итоге просто завершил всю программу с помощью сигналов, с которыми я тестировал.
Если вы блокируете стороннюю библиотеку, которая зацикливается на EINTR, вы можете рассмотреть комбинацию использования pthread_kill с сигналом (USR1 и т. д.), Вызывающим пустую функцию (не SIG_IGN) с фактическим закрытием / заменой дескриптора файла в вопрос. Используя dup2 для замены fd на / dev / null или аналогичный, вы заставите стороннюю библиотеку получить результат «конец файла» при повторной попытке чтения.
Обратите внимание: если сначала выполнить dup () исходный сокет, вам не нужно будет закрывать сокет.
Канонический способ сделать это - использовать pthread_cancel, где поток выполняет pthread_cleanup_push / pop, чтобы обеспечить очистку всех используемых ресурсов.
К сожалению, это НЕЛЬЗЯ использовать в коде C++. Любой код C++ std lib или ЛЮБОЙ try {} catch() в вызывающем стеке во время pthread_cancel потенциально может убить весь ваш процесс segvi.
Единственный обходной путь - обработать SIGUSR1, установив флаг остановки, pthread_kill(SIGUSR1), а затем в любом месте, где поток заблокирован при вводе-выводе, если вы получите EINTR, проверьте флаг остановки перед повторной попыткой ввода-вывода. На практике это не всегда удается в Linux, не знаю почему.
Но в любом случае бесполезно говорить о том, нужно ли вызывать какую-либо стороннюю библиотеку, потому что у них, скорее всего, будет жесткий цикл, который просто перезапускает ввод-вывод на EINTR. Обратное проектирование их файлового дескриптора, чтобы закрыть его, тоже не повредит - они могут ждать семафор или другой ресурс. В этом случае написать рабочий код просто невозможно, точка. Да, это серьезно повреждено. Поговорите с ребятами, которые разработали исключения C++ и pthread_cancel. Предположительно это может быть исправлено в какой-нибудь будущей версии C++. Удачи с этим.
Что вы имеете в виду под фразой «На практике это не всегда удается в Linux, не знаю почему». (@qqq). Его можно подключить к этому lwn.net/Articles/683118
Старый вопрос, на который вполне может быть дан новый ответ по мере развития событий, и теперь доступна новая технология для обработки сигналов лучше в потоках.
Начиная с ядра Linux 2.6.22, система предлагает новую функцию signalfd(), которую можно использовать для открытия файлового дескриптора для заданного набора сигналов Unix (кроме тех, которые полностью уничтожают процесс).
// defined a set of signals
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
// ... you can add more than one ...
// prevent the default signal behavior (very important)
sigprocmask(SIG_BLOCK, &set, nullptr);
// open a file descriptor using that set of Unix signals
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
Теперь вы можете использовать функции poll() или select() для прослушивания сигнала с помощью более обычного файлового дескриптора (сокета, файла на диске и т. д.), Который вы слушали.
NONBLOCK важен, если вам нужен цикл, который может проверять сигналы и другие файловые дескрипторы снова и снова (т.е. он также важен для вашего другого файлового дескриптора).
У меня есть такая реализация, которая работает с (1) таймерами, (2) сокетами, (3) каналами, (4) сигналами Unix, (5) обычными файлами. Собственно, действительно любой файловый дескриптор плюс таймеры.
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.h
Также вас могут заинтересовать такие библиотеки, как Libevent
qqq, похоже, дает правильный ответ, который, к сожалению, набрал очень мало голосов.
pthread_cancel- это решение вашей проблемы.