On Thu, Nov 05, 2009 at 03:02:57PM +0400, Rinat Bikov wrote: > Передо мной стоит задача: написать динамическую библиотеку, которая > могла бы загружать и выгружать другие динамические библиотеки из > главной функции таким образом, чтобы загружаемые динамические > библиотеки имели доступ к интерфейсу загружающей их динамической > библиотеки (:-)). > Притом загружаемые динамические библиотеки могут зацикливаться, > соответственно необходимо, чтобы они убивались по таймауту. > Для решения этой задачи я решил воспользоваться pthreads. > Проблему того, что потоки с зацикливающейся функцией не убиваются > стандартно pthread_cancel решил при помощи отправки > pthread_kill(SIGTERM) + добавил обработчик этого сигнала в потоке: > signal(SIGTERM, sig_handler); Обработчики сигналов на самом деле устанавливаются глобально для всего процесса (а вот маска заблокированных сигналов, управляемая через pthread_sigmask(), действительно своя у каждого потока). Кроме того, по стандарту POSIX.1-2001 функция signal() не должна использоваться в многопоточных программах - нужно использовать sigaction() (впрочем, это стоит делать в любом случае, поскольку поведение signal() может отличаться в зависимости от параметров, используемых при сборке). Однако на самом деле это "решение" создаёт новые проблемы. По умолчанию все потоки используют режим PTHREAD_CANCEL_DEFERRED, в котором вызовы pthread_cancel() для потока обрабатываются только в момент выполнения потоком функций, перечисленных в pthreads(7) в списке "Cancellation Points" (точнее, на самом деле этот список несколько шире - в этой документации перечислены только функции, входящие в стандарты POSIX). Если поток зацикливается без вызова таких функций, его действительно не получится прервать вызовом pthread_cancel(). Однако вызовом функции pthread_setcanceltype() с параметром PTHREAD_CANCEL_ASYNCHRONOUS можно включить асинхронную обработку pthread_cancel() - в этом случае вызов pthread_cancel() прерывает выполнение потока в любом состоянии. Проблема в том, что после установки режима PTHREAD_CANCEL_ASYNCHRONOUS можно вызывать только те функции libc, которые отмечены в документации как async-cancel-safe - а по стандарту POSIX.1-2001 в эту группу входят только 3 функции: pthread_cancel(), pthread_setcancelstate(), pthread_setcanceltype(). В результате такой режим можно устанавливать только для кода, который выполняет только вычисления, при этом не обращаясь к файлам, не используя динамическое распределение памяти и других общих ресурсов; в случае C++ нужно учитывать, что безобидно выглядящий код может на самом деле вызывать функции STL и прочих библиотек, где используется operator new и т.п. В случае использования pthread_kill() обработчик указанного сигнала вызывается в контексте указанного потока асинхронно (если в этот момент сигнал в потоке не заблокирован). В приложенном примере кода этот обработчик вызывает fprintf() (чего, кстати, делать нельзя - список async-signal-safe функций есть в signal(7); в принципе можно вызывать write()), это cancellation point, в результате обрабатывается отложенный вызов pthread_cancel(). Однако таким образом фактически происходит преобразование режима PTHREAD_CANCEL_DEFERRED в PTHREAD_CANCEL_ASYNCHRONOUS, со всеми его проблемами (посланный через pthread_kill() сигнал мог прервать выполнение потока, например, в середине функции типа malloc(), и прерывание работы потока в этом состоянии приведёт к повреждению общих структур данных и нарушению работы других потоков). (На самом деле реализация функции pthread_cancel() в glibc использует сигналы для прерывания потоков, но в режиме PTHREAD_CANCEL_DEFERRED соответствующий сигнал большую часть времени заблокирован, и разблокируется только перед выполнением системных вызовов в функциях, являющихся cancellation point; в режиме PTHREAD_CANCEL_ASYNCHRONOUS этот сигнал разблокирован.) Для решения исходной задачи (прерывание операций по таймауту) можно предложить следующие варианты: 1) Аккуратно писать код, который нужно прерывать: в участках, где выполняются длительные вычисления без обращения к функциям, не допускающим прерывания, включать PTHREAD_CANCEL_ASYNCHRONOUS, в остальном коде использовать PTHREAD_CANCEL_DEFERRED и пары pthread_cleanup_push()/pthread_cleanup_pop() для освобождения ресурсов при прерывании выполнения потока (впрочем, деструкторы C++ для автоматических объектов при этом всё-таки вызываются). 2) Создавать для выполнения кода, который необходимо прерывать, не поток, а полноценный процесс - в этом случае нет проблем с порчей структур в памяти (за исключением случая, когда для обмена информацией между процессами используется разделяемая память). [...] > Также прилагается strace удачного и неудачного запуска. strace для многопоточных программ надо запускать с опцией -f, иначе отслеживается только выполнение главного потока.