2008-10-15 2 views
23

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

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

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

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

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

+0

QQQ, кажется, правильный ответ, который, к сожалению, имеет очень мало голосов. `pthread_cancel` - это решение вашей проблемы. – 2010-09-27 03:30:19

+0

Пока поток остается заблокированным, он не может навредить. Проблема в том, что поток просыпается, когда вы закрываете все. Таким образом, исправление заключается в том, чтобы поместить некоторый код после строки, которая блокирует это, останавливает поток от выполнения чего-либо * else *, если завершение работы происходит. – 2011-09-28 17:15:12

+0

Обсуждаются аналогичные проблемы и возможные решения: [Дескрипторы файлов и многопоточные программы] (http://www.ddj.com/hpc-high-performance-computing/212001285) – dmityugov 2008-11-10 12:58:48

ответ

2

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

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

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

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

3

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

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

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

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

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

9

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

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

6

Зависит от того, как он ждет ввода-вывода.

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

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

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

1

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

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

0

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

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

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

13

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

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

} 
0

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

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

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

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

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

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

0
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 - это конкретная переменная потока, которая может использоваться в сочетании с сигналом для уничтожения нити.

Что касается раздела IO дескриптора, вам нужно убедиться, что вы использовали open с опцией O_NOBLOCK, или если в его гнезде есть аналогичный флаг, вы можете установить MSG_NOWAIT ??. для других fds im not sure

1

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

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

1

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

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

12

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

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

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

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