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


81

Я намагався зрозуміти тонкощі взаємодії потоків POSIX та сигналів POSIX. Мене, зокрема, цікавить:

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

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

Відповіді:


48
  • Який найкращий спосіб контролювати, до якого потоку подається сигнал?

Як зазначив @ zoli2k, чітке призначення одного потоку для обробки всіх сигналів, які ви хочете обробити (або набору потоків, кожен із яких має певні обов'язки щодо сигналів), є хорошим методом.

  • Який найкращий спосіб повідомити інший потік (який насправді може бути зайнятий), що сигнал надійшов? [...]
  • Як я можу безпечно обробляти інформацію про те, що сигнал стався до інших потоків? Чи це повинно відбуватися в обробнику сигналу?

Я не скажу "найкраще", але ось моя рекомендація:

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

Найпростіший спосіб зробити це, щоб потік приймав сигнали в циклі, використовуючи sigwaitinfoабоsigtimedwait . Потім потік якось перетворює сигнали, можливо, транслюючи a pthread_cond_t, прокидаючи інші потоки з більшою кількістю вводу-виводу, ставлячи команду в чергу, безпечну для потоку, безпечну чергу.

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

( UPDATED @caf справедливо зазначає, що sigwaitпідходи вищі.)


1
Це набагато корисніша відповідь, тим більше, що її також можна використовувати для обробки нефатальної обробки сигналів. Дякую!
Donal Fellows

1
Найпростіше, якщо потік обробки сигналів взагалі не встановлює обробники сигналів - натомість він циклічно вмикається sigwaitinfo()(або sigtimedwait()), а потім відправляє їх до решти програми, як описано в останньому параграфі.
кафе

@caf, справді так. Оновлено
pilcrow

14

Відповідно до стандарту POSIX, всі потоки повинні відображатися в системі з однаковим ПІД, і за допомогою pthread_sigmask()ви можете визначити маску блокування сигналу для кожного потоку.

Оскільки дозволяється визначати лише один обробник сигналу для PID, я вважаю за краще обробляти всі сигнали в одному потоці та надсилати, pthread_cancel()якщо поточний потік потрібно скасувати. Це кращий спосіб проти, pthread_kill()оскільки він дозволяє визначити функції очищення для потоків.

У деяких старих системах через відсутність належної підтримки ядра запущені потоки можуть відрізнятися PID від PID батьківського потоку. Див. Поширені запитання щодо обробки сигналів за допомогою linuxThreads на Linux 2.4 .


У тому, що ви кажете "реалізовано", що означає? Крім того, невірно завжди нукувати інші потоки у відповідь на сигнал (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.


3

Сигнали IMHO, Unix V та потоки posix погано змішуються. Unix V - це 1970. POSIX - 1980;)

Є пункти відміни, і якщо ви дозволите сигнали та pthreads в одному додатку, ви врешті-решт закінчите писати цикли навколо кожного дзвінка, що може напрочуд повернути EINTR.

Отже, те, що я робив у (небагатьох) випадках, коли мені доводилося програмувати багатопоточність на Linux або QNX, було, щоб замаскувати всі сигнали для всіх (крім одного) потоків.

Коли надходить сигнал Unix V, процес перемикає стек (це було стільки ж паралелізму в Unix V, скільки ви могли отримати в процесі).

Як натякають інші публікації тут, можливо, зараз можна сказати Системі, який потік posix повинен стати жертвою цього перемикання стека.

Після того, як вам вдалося налагодити роботу потоку обробника сигналу, залишається питання, як перетворити інформацію про сигнал у щось цивілізоване, що можуть використовувати інші потоки. Потрібна інфраструктура для міжпотокового зв'язку. Одним з корисних шаблонів є шаблон актора, де кожен з ваших потоків є ціллю для якогось механізму обміну повідомленнями в процесі.

Отже, замість того, щоб скасовувати інші потоки чи вбивати їх (або інші дивні речі), вам слід спробувати провести маршал Сигналу з контексту Сигналу до потоку обробника Сигналу, а потім скористатися механізмами комунікації шаблону актора для надсилання семантично корисних повідомлень цим акторам, яким потрібна інформація, що стосується сигналу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.