Потоки и сигналы POSIX

81

Я пытался понять тонкости взаимодействия потоков POSIX и сигналов POSIX. В частности, меня интересуют:

  • Как лучше всего контролировать, в какой поток доставляется сигнал (при условии, что это не фатально)?
  • Как лучше всего сообщить другому потоку (который действительно может быть занят), что сигнал прибыл? (Я уже знаю, что использовать переменные условия pthread из обработчика сигналов - плохая идея.)
  • Как я могу безопасно обрабатывать передачу информации о том, что сигнал поступил в другие потоки? Должно ли это происходить в обработчике сигналов? (Я вообще не хочу убивать другие потоки; мне нужен гораздо более тонкий подход.)

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

Donal Fellows
источник

Ответы:

48
  • Как лучше всего контролировать, в какой поток доставляется сигнал?

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

  • Как лучше всего сообщить другому потоку (который действительно может быть занят), что сигнал прибыл? [...]
  • Как я могу безопасно обрабатывать передачу информации о том, что сигнал поступил в другие потоки? Должно ли это происходить в обработчике сигналов?

Я не буду говорить «лучше всего», но вот моя рекомендация:

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

Самый простой способ сделать это - заставить поток принимать сигналы в цикле с помощью sigwaitinfoилиsigtimedwait . Затем поток каким-то образом преобразует сигналы, возможно, транслируя pthread_cond_t, пробуждая другие потоки с большим количеством операций ввода-вывода, помещая команду в очередь, ориентированную на конкретное приложение, потокобезопасную, что угодно.

В качестве альтернативы, специальный поток может позволять доставлять сигналы обработчику сигналов, демаскируя их для доставки только тогда, когда они готовы обработать сигналы. (Однако доставка сигнала через обработчики обычно более подвержена ошибкам, чем прием сигнала через sigwaitсемейство.) В этом случае обработчик сигнала получателя выполняет некоторые простые и безопасные для асинхронного сигнала действия: установка sig_atomic_tфлагов, вызов sigaddset(&signals_i_have_seen_recently, latest_sig), write() байта к неблокирующему собственному каналу и т. д. Затем, вернувшись в свой замаскированный основной цикл, поток сообщает о получении сигнала другим потокам, как указано выше.

( ОБНОВЛЕНИЕ @caf справедливо указывает на sigwaitпревосходство подходов.)

Pilcrow
источник
1
Это гораздо более полезный ответ, тем более, что его можно использовать также для обработки нефатальных сигналов. Благодаря!
Donal Fellows
1
Проще всего, если поток обработки сигналов вообще не устанавливает обработчики сигналов - вместо этого он зацикливается на sigwaitinfo()(или sigtimedwait()), а затем отправляет их остальной части приложения, как описано в последнем абзаце.
caf,
@caf, действительно так. Обновлено
pilcrow
14

В соответствии со стандартом POSIX все потоки должны появляться в системе с одним и тем же PID, и с помощью pthread_sigmask()вы можете определить маску блокировки сигнала для каждого потока.

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

В некоторых старых системах из-за отсутствия надлежащей поддержки ядра запущенные потоки могут иметь PID, отличный от PID родительского потока. См. FAQ по обработке сигналов с помощью linuxThreads в Linux 2.4 .

zoli2k
источник
В том, что вы говорите, что означает «реализовано»? Кроме того, неправильно всегда нейтрализовать другие потоки в ответ на сигнал (SIGHUP и SIGWINCH требуют большей тонкости), и тем не менее небезопасно использовать переменные условия для сообщения другим потокам. Плохой ответ.
Donal Fellows
1
Мой отрицательный голос был удален, но это все еще недостаточный ответ, потому что я не могу просто убивать потоки в ответ на сигнал. В некоторых случаях я собираюсь помещать события в очередь локально в ответ, в других мне нужно очень осторожно разрывать потоки (кстати, у меня уже есть большая часть оборудования для выполнения этих частей; это подключения к ОС сигналы, которые отсутствуют).
Donal Fellows
1
@ zoli2k: Я только что пробовал работать make menuconfigсо свежеклонированной веткой git master uClibc. Там является выбор между старым LinuxThreads и новой NPTL как реализации резьбы по стандарту POSIX, но помощи от года 2012 по- прежнему рекомендует против выбора NPTL. Таким образом, в современных встроенных системах Linux до сих пор часто встречается устаревшая реализация LinuxThreads, даже если в системе используется достаточно новое ядро ​​Linux.
FooF
3

Где я сейчас:

  • Сигналы бывают разных основных классов, некоторые из которых обычно должны просто убить процесс (SIGILL), а некоторые из них никогда не нуждаются в каких-либо действиях (SIGIO; в любом случае проще просто выполнить асинхронный ввод-вывод). Эти два класса не требуют действий.
  • Некоторые сигналы не требуют немедленной обработки; подобные SIGWINCH могут быть поставлены в очередь до тех пор, пока это не будет удобно (как событие от X11).
  • Сложные - это те, когда вы хотите отреагировать на них, прерывая то, что вы делаете, но не доходя до такой степени, что стираете нить. В частности, SIGINT в интерактивном режиме должен оставлять вещи отзывчивыми.

У меня еще есть перебирать signalпротив sigaction, pselect, sigwait, sigaltstack, и целая куча других кусочков POSIX (и не-POSIX) API.

Donal Fellows
источник
3

IMHO, сигналы Unix V и потоки posix плохо сочетаются. Unix V - 1970. POSIX - 1980;)

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

Итак, что я делал в (нескольких) случаях, когда мне приходилось программировать многопоточность в Linux или QNX, так это замаскировать все сигналы для всех (кроме одного) потоков.

Когда поступает сигнал Unix V, процесс переключает стек (в Unix V это было столько параллелизма, сколько вы могли получить внутри процесса).

Как намекают другие сообщения здесь, теперь можно было бы сообщить Системе, какой поток posix должен стать жертвой этого переключения стека.

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

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

user2173833
источник