Подтвердить что ты не робот

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

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

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

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

Это на Linux и с помощью pthreads.

Редактирование: я немного играл с SIGIO и SIGALRM, когда я не устанавливаю обработчик сигналов, они прерывают блокировку ввода-вывода, но дают сообщение на консоли (возможно "входы/выходы" ), но когда я устанавливаю обработчик сигнала, чтобы избежать этого сообщения, они больше не прерывают блокировку IO, поэтому поток не заканчивается. Поэтому я возвращаюсь к первому шагу.

4b9b3361

Ответ 1

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

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

Ответ 2

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

Обычно для передачи сигнала в поток используется 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);

}

Ответ 3

Канонический способ сделать это с помощью pthread_cancel, где поток выполнил pthread_cleanup_push/pop, чтобы обеспечить очистку для любых ресурсов, которые он использует.

К сожалению, это НЕ МОЖЕТ использоваться в коде на С++. Любой код С++ std lib или ANY try {} catch() в вызывающем стеке во время pthread_cancel потенциально могут убить весь ваш процесс.

Единственным обходным решением является обработка SIGUSR1, установка флага остановки, pthread_kill(SIGUSR1), а затем где-либо поток блокируется при вводе-выводе, если вы получите EINTR проверить флаг остановки перед повторной попыткой ввода-вывода. На практике это не всегда удается в Linux, не знаю почему.

Но в любом случае бесполезно говорить о том, нужно ли вам звонить какой-либо третьей стороне lib, потому что они, скорее всего, будут иметь жесткий цикл, который просто перезапускает ввод-вывод на EINTR. Обратное проектирование их файлового дескриптора, чтобы закрыть его, также не сократит его - они могут ждать на семафоре или другом ресурсе. В этом случае просто невозможно написать рабочий код, период. Да, это полностью повреждено мозгом. Поговорите с парнями, которые разработали исключения С++ и pthread_cancel. Предположительно это может быть исправлено в некоторой будущей версии С++. Удачи вам в этом.

Ответ 4

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

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

Ответ 5

Зависит от ожидания ожидания ввода-вывода.

Если поток находится в состоянии "Бесперебойное IO" (отображается как "D" сверху), то на самом деле вы абсолютно ничего не можете с этим поделать. Обычно потоки только вводят это состояние, делая что-то вроде ожидания перехода страницы (или требуемой загрузки, например, из файла mmap'd или общей библиотеки и т.д.), Однако сбой (особенно сервер NFS) может вызвать он должен оставаться в этом состоянии дольше.

Поистине нет способа избежать этого состояния "D". Нить не будет реагировать на сигналы (их можно отправить, но они будут поставлены в очередь).

Если это нормальная функция ввода-вывода, такая как read(), write() или функция ожидания, например select() или poll(), сигналы будут доставлены нормально.

Ответ 6

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

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

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

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


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

Ответ 7

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

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

Ответ 8

Я всегда добавляю функцию "kill", связанную с функцией потока, которую я запускаю до объединения, которая гарантирует, что поток будет соединяться в разумные сроки. Когда поток использует блокировку IO, я пытаюсь использовать систему для разрыва блокировки. Например, при использовании сокета я бы отключил выключение вызова (2) или закрыть (2) на нем, что приведет к тому, что сетевой стек завершит его чисто.

Реализация сокетов Linux является потокобезопасной.

Ответ 9

Я удивлен, что никто не предложил pthread_cancel. Недавно я написал многопоточную программу ввода-вывода и вызвал функцию cancel(), после чего команда join() работала просто отлично.

Я изначально попробовал pthread_kill(), но закончил просто завершение всей программы сигналами, которые я тестировал.

Ответ 10

Если вы блокируете стороннюю библиотеку, которая работает в EINTR, вам может потребоваться комбинация использования pthread_kill с сигналом (USR1 и т.д.), вызывающая пустую функцию (не SIG_IGN) с фактическим закрытием/заменой файловый дескриптор. Используя dup2, чтобы заменить fd на /dev/null или аналогичный, вы заставите стороннюю библиотеку получить результат конца файла при повторном чтении.

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

Ответ 11

Сигналы и поток - это тонкая проблема в Linux по разным страницам руководства. Используете ли вы LinuxThreads или NPTL (если вы работаете в Linux)?

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

Вы должны использовать timed select или poll и установить глобальный флаг для завершения потока.

Ответ 12

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

Когда событие i/o запущено, условное обозначение должно быть сообщено.

Основной поток может просто сигнализировать об этом условии, перебирая предикат цикла на false.

что-то вроде:

while (!_finished)
{
    pthread_cond_wait(&cond);
    handleio();
}
cleanup();

Помните с условными переменными, чтобы правильно обрабатывать сигналы. У них могут быть такие вещи, как "ложные пробуждения". Поэтому я бы обернул вашу собственную функцию вокруг функции cond_wait.

Ответ 13

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 im not sure