Как присоединиться к потоку, который висит на блокировке ввода-вывода?

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

Как мне правильно разрешить эту ситуацию? Должен ли я послать pthread_kill (theard, SIGIO) или pthread_kill (theard, SIGALRM), чтобы сломать блок? Это вообще правильный сигнал? Или есть другой способ решить эту ситуацию и позволить этому дочернему потоку выйти из блокирующего чтения?

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

Это в Linux и с использованием pthreads.

Обновлено: я немного поигрался с SIGIO и SIGALRM, когда я не устанавливаю обработчик сигнала, они нарушают блокирующий ввод-вывод, но выдают сообщение на консоли («Возможен ввод-вывод»), но когда я устанавливаю обработчик сигнала , чтобы избежать этого сообщения, они больше не нарушают блокирующий ввод-вывод, поэтому поток не завершается. Так что я как бы вернулся к первому шагу.

qqq, похоже, дает правильный ответ, который, к сожалению, набрал очень мало голосов. pthread_cancel - это решение вашей проблемы.

R.. GitHub STOP HELPING ICE 27.09.2010 07:30

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

David Schwartz 28.09.2011 21:15

Там же обсуждается похожая проблема и возможные решения: Дескрипторы файлов и многопоточные программы

dmityugov 10.11.2008 15:58

Статья дала мне именно то, что я искал - shutdown (fd, SHUT_RDWR) ;. Спасибо.

sivabudh 28.06.2010 23:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
4
18 253
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Я думаю, как вы сказали, единственный способ - это послать сигнал, а затем уловить его и обработать соответствующим образом. Альтернативными вариантами могут быть SIGTERM, SIGUSR1, SIGQUIT, SIGHUP, SIGINT и т. д.

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

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

Идея состоит в том, чтобы создать файл из основного цикла (или по одному на поток, как предполагает тайм-аут - это даст вам более точный контроль над тем, какие потоки просыпаются). Все потоки, которые блокируют файловый ввод-вывод, будут выполнять select (), используя файл (ы), с которыми они пытаются работать, а также файл, созданный основным циклом (как член чтения набор дескрипторов файла). Это должно вернуть все вызовы select ().

Код для обработки этого «события» из основного цикла нужно будет добавить в каждый из потоков.

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


Я не могу сказать наверняка, работает ли это, поскольку реструктуризация означала, что необходимость попробовать это отпала.

Ваш select() может иметь тайм-аут, даже если он случается нечасто, для корректного выхода из потока при определенных условиях. Я знаю, опрос - отстой ...

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

или у вас может быть один канал для всех потоков, статус «готов» возвращается из выбора / опроса нескольким потокам, ожидающим одного дескриптора файла (пока он запущен на уровне). Таким образом, все потоки, ожидающие одного «убийственного» канала, получат уведомление о смерти.

Greg Rogers 17.06.2009 01:40

Зависит от того, как ждёт 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, поскольку он находится в сторонней библиотеке, а не в моем собственном коде, я полностью пропустил этот факт, и поэтому простой сигнал не делаю то, что ожидал.

Grumbel 17.10.2008 22:53

Можно ли с помощью этого метода освободить ресурсы, полученные с помощью flockfile?

Ynv 27.04.2012 13:13

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

Когда запускается событие ввода-вывода, должно быть указано условие.

Основной поток может просто сигнализировать об условии, изменяя предикат цикла на 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

Juraj Michalak 02.01.2019 18:16
Ответ принят как подходящий

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

Начиная с ядра 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

Другие вопросы по теме