Практика работы с сигналами

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

    Важные факты о сигналах:
    • Сигналы в Linux играют роль некоего средства межпроцессного взаимодействия (а так же и межпоточного)
    • Каждый процесс имеет маску сигналов (сигналов, получение которых он игнорирует)
    • Каждая нить (thread), так же как и процесс, имеет свою маску сигналов
    • При получении сигнала(если он не блокируется) процесс/нить прерывается, управление передается в функцию обработчик сигнала, и если эта функция не приводит к завершению процесса/нити, то управление передается в точку на которой процесс/нить была прервана
    • Можно установить свою функцию обработчик сигнала, но только для процесса. Данный обработчик будет вызываться и для каждой нити порожденной из этого процесса

    Я не буду углубляться в теорию сигналов, что откуда зачем и куда. Меня в первую очередь интересует сам механизм работы с ними. Поэтому в качестве используемых сигналов будут выступать SIGUSR1 и SIGUSR2, это два единственных сигнала отданных в полное распоряжение пользователя. А так же я постараюсь уделить больше внимание именно межпоточному взаимодействию сигналов.
    Итак, поехали.

    Функция обработчик сигналов


    Данная функция вызывается, когда процесс (или нить) получает неблокируемый сигнал. Дефолтный обработчик завершает наш процесс (нить). Но мы можем сами определить обработчики для интересующих нас сигналов. Следует очень осторожно относится к написанию обработчика сигналов, это не просто функция, выполняющаяся по коллбеку, происходит прерывание текущего потока выполнения без какой либо подготовительной работы, таким образом глобальные объекты могут находится в неконсистентном состоянии. Автор не берется приводить свод правил, так как сам их не знает, и призывает последовать совету Kobolog (надеюсь он не против, что я ссылаюсь на него) и изучить хотя бы вот этот материал FAQ.
    void hdl(int sig)
    {
    	exit = true;
    }
    

    Установить новый обработчик сигнала можно двумя функциями
    sighandler_t signal(int signum, sighandler_t handler);
    

    Которая принимает номер сигнала, указатель на функцию обработчик (или же SIG_IGN (игнорировать сигнал) или SIG_DFL (дефолтный обработчик)), и возвращает старый обработчик. Сигналы SIGKILL и SIGSTOP не могут быть «перехвачены» или проигнорированы. Использование этой функции крайне не приветствуется, потому что:
    • функция не блокирует получение других сигналов пока выполняется текущий обработчик, он будет прерван и начнет выполняться новый обработчик
    • после первого получения сигнала (для которого мы установили свой обработчик), его обработчик будет сброшен на SIG_DFL

    Этих недостатков лишена функция
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    

    Которая также принимает номер сигнала (кроме SIGKILL и SIGSTOP). Второй аргумент это новое описание для сигнала, через третий возвращается старое значение. Структура struct sigaction имеет следующие интересующие нас поля
    • sa_handler — аналогичен sighandler_t в функции signal
    • sa_mask — маска сигналов который будут блокированы пока выполняется наш обработчик. + по дефолту блокируется и сам полученный сигнал
    • sa_flags — позволяет задать дополнительные действия при обработке сигнала о которых лучше почитать тут

    Использование данной функции выглядит совсем просто
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = hdl;
    sigset_t   set; 
    sigemptyset(&set);                                                             
    sigaddset(&set, SIGUSR1); 
    sigaddset(&set, SIGUSR2);
    act.sa_mask = set;
    sigaction(SIGUSR1, &act, 0);
    sigaction(SIGUSR2, &act, 0);
    

    Здесь мы установили наш обработчик для сигналов SIGUSR1 и SUGUSR2, а также указали, что необходимо блокировать эти же сигналы пока выполняется обработчик.
    С обработчиком сигналов есть один не очень удобный момент, он устанавливается на весь процесс и все порожденные нити сразу. Мы не имеет возможность для каждой нити установить свой обработчик сигналов.
    Но при этом следует понимать что когда сигнал адресуется процессу, обработчик вызывается именно для главной нити (представляющей процесс). Если же сигнал адресуется для нити, то обработчик вызывается из контекста этой нити. См пример 1.

    Блокирование сигналов


    Для того, чтобы заблокировать некоторый сигналы для процесса, необходимо добавить их в маску сигналов данного процесса. Для этого используется функция
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    

    Мы можем к уже существующей маске сигналов добавить новые сигналы (SIG_BLOCK), можем из этой маски убрать часть сигналов (SIG_UNBLOCK), а так же установить полностью нашу маску сигналов (SIG_SETMASK).
    Для работы с маской сигналов внутри нити используется функция
    int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset);
    

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

    sigwait


    Данная функция позволяет приостановить выполнении процесса (или нити) до получения нужного сигнала (или одного из маски сигналов). Особенностью этой функции является то, что при получении сигнала не будет вызвана функции обработчик сигнала. См. пример 2.

    Посыл сигнала


    Для того, чтобы послать сигнал процессу можно использовать две функции
    int kill(pid_t pid, int sig);
    int raise(int sig);
    

    С первой все понятно. Вторая нужна для того, чтобы послать сигнал самому себе, и по сути равносильна kill(getpid(), signal). Функция getpid() возвращает PID текущего процесса.
    Для того, чтобы послать сигнал отдельной нити, используется функция
    int pthread_kill(pthread_t thread, int sig);
    


    Пример использования сигналов


    Все, что я описал выше, не дает ответа на вопрос «Зачем мне использовать сигналы». Теперь я хотел бы привести реальный пример использования сигналов и где без них попросту не обойтись.
    Представьте, что вы хотите читать или писать какие-то данные в какое то устройство, но это может привести к блокированию. Ну например, чтение в случае работы с сокетами. Или может быть запись в пайп. Вы можете вынести это в отдельный поток, чтобы не блокировать основную работу. Но что делать когда вам нужно завершить приложение? Как корректно прервать блокирующую операцию IO? Можно было бы задавать таймаут, но это не очень хорошее решение. Для этого есть более удобные средства: функции pselect и ppoll. Разница между ними исключительно в юзабельности, поведение у них одинаковое. В первую очередь эти функции нужны для мультиплексирования работы с IO (select/poll). Префикс 'p' в начале функции указывает на то, что данная функция может быть корректно прервана сигналом.

    Итак, сформулируем требование:
    Необходимо разработать приложение, открывающее сокет (для простоты UDP) и выполняющее в потоке операцию чтения. Данное приложение должно корректно без задержек завершаться по требованию пользователя.
    Функция треда выглядит вот так
    void* blocking_read(void* arg)
    {
    	if(stop)
    	{
    		// не успели стартовать, а нас уже прикрыли ?
    		std::cout << "Thread was aborted\n";
    		pthread_exit((void *)0); 
    	}
    		
    	// Блокируем сигнал SIGINT
    	sigset_t set, orig; 
    	sigemptyset(&set);
    	sigaddset(&set, SIGINT);
    	sigemptyset(&orig);
    	pthread_sigmask(SIG_BLOCK, &set, &orig);
    	
    	if(stop)
    	{
    		// пока мы устанавливали блокировку сигнала он уже произошол
    		// возвращаем все как было и выходим
    		std::cout << "Thread was aborted\n";
    		pthread_sigmask(SIG_SETMASK, &orig, 0);
    		pthread_exit((void *)0);
    	}
    		
    	// Здесь нас не могут прервать сигналом SIGINT
    	std::cout << "Start thread to blocking read\n";
    	
    	// ...
    	// создаем, настраиваем сокет, подготавливаем структуру для ppoll
        
        ppoll((struct pollfd*) &clients, 1, NULL, &orig);
        
        if(stop)
        {
        	// получили сигнал о завершении работы
        	std::cout << "Thread was aborted\n";
        	close(sockfd);
        	pthread_sigmask(SIG_SETMASK, &orig, 0);
        	// сдесь сигнал SIGINT все еще заблокирован
        }
        
        // Мы либо считали данные, либо произошла какаято ошибка. Но мы не получали 
        // сигнала о завершении работы и продолжаем работать "по плану"
        
     	close(sockfd);   
    	pthread_exit((void *)0);  
    }
    

    stop это глобальный булев флаг который устанавливается в true нашим обработчиком, что сообщает потоку о необходимости завершиться.
    Логика работы такая:
    • проверяем, что пока стартовал тред его еще не пожелали завершить
    • блокируем завершающий сигнал
    • проверяем, что пока блокировали, нас не пожелали завершить
    • вызываем ppoll передавая в качестве последнего параметра маску сигналов по которой ждется сигнал
    • после выхода из ppoll проверяем что вышли не из за сигнала о завершении

    Вот так выглядит главная функция
    int main()
    {
    ...
    	struct sigaction act;
    	memset(&act, 0, sizeof(act));
    	act.sa_handler = hdl;
    
    	sigemptyset(&act.sa_mask);                                                             
    	sigaddset(&act.sa_mask, SIGINT); 
    	sigaction(SIGINT, &act, 0);
    	
    ...
    	pthread_kill(th1, SIGINT);
    ...
    }
    

    Устанавливаем наш обработчик для SIGINT, и когда нужно завершить дочерний поток шлем ему этот сигнал.
    Полный листинг см. пример 3.

    На мой взгляд, недостатком данного способа является то, что в случае нескольких потоков мы можем завершить их только все сразу. Нет возможности устанавливать свой обработчик сигналов для каждого треда. Таким образом, нет возможности реализовать полноценное межпоточное взаимодействие через сигналы. Linux way это не предусматривает.

    PS. Исходные коды разместил на сервисе PasteBin (ссылку не даю, а то еще за рекламу посчитают).
    PPS. Прошу простить за обилие ошибок. Язык, слабая моя сторона. Спасибо, всем кто помог их исправить.



    Данная статья не претендует на полное (и глубокое) описание работы с сигналами и нацелена в первую очередь на тех, кто до этого момента не сталкивались с понятием «сигнал». Для более глубоко понимания работы сигналов автор призывает обратиться в более компетентные источники и ознакомиться с конструктивной критикой в комментариях.
    Поделиться публикацией

    Похожие публикации

    Комментарии 61

      +7
      Доколе ж на Хабре будут писать «итак» раз дель но. Это ж полный швах, почти как жи-ши.
        –4
        простите, плохо меня учили в школе.
          –3
          Не бывает плохо учили, бывает плохо учился.
        +2
        первое правило работы с сигналами в с++: никогда не используйте в обработчиках нефатальных сигналов ни для чего другого кроме как выставить значение PoD переменной.
          +12
          Первое правило работы с сигналами, на самом деле, такое: из обработчика сигналов безопасно можно менять только значения переменных типа volatile sig_atomic_t и больше ничего. Вот в этом FAQ очень хорошо написано что можно делать из обработчика сигналов, а что нет.
            –4
            Безопасно можно менять и без volatile, если место где происходит проверка этой переменной вызывается только время от времени. Конечно для совсем неразбирающихся в том до какого вида компилятор може соптимайзить код лучше внушить про volatile :)
              +8
              WAT? Что значит время от времени? Вы сейчас хотите сказать, что компилятор будет «время от времени» делать load на переменную просто так? Без volatile flow analysis покажет, что переменная никогда не записывается кроме инициализации, и никакое «время от времени» само по себе не сработает.
                –4
                О боже :) вы представляете вообще себе не код аля в этой статье а большую программу? :)
                например:

                init_some_variables(); // тут мы чтото делаем, в том числе инициализируем многострадальную не volatile переменную в классе A, заодно и создаем экземпляр класса.

                check_incoming_connections(); // чтонить вроде poll, accept, добавления сокетов в контейнер, создание обработчика событий на сокете и тп

                do_some_job(); // тут мы, например, делаем запрос в sql базу и чтонить там лопатим

                what_about_our_signal_variable(); // тут мы проверяем ту самую переменную, которая как написано выше является полем класса созданного в куче.

                и все это в одном потоке исполнения :)

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

                P.S. да и таки изменять переменную все равно безопасно даже учитывая то что компилятор может ее сделать регистровой и незаметить «изменение» в сигнале :) ничего же страшного не произойдет :) просто тот участок кода что следит за переменной не узнает о изменении :)))

                  +16
                  Я дам вам совет: остановиться сейчас и не выставлять себя ещё большим дураком, окей?

                  Стандарт C++, ISO-IEC 14882, 3rd Edition, пункт 1.9.6:
                  When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
                  — of type volatile std::sig_atomic_t nor
                  — lock-free atomic objects (29.4)
                  are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined

                  Ваш отличный план опустить volatile приводит к undefined behavior, что означает, что весь ваш код, который создаёт сокеты, ходить в mysql и так далее, можно спокойно выбросить, потому что он — говно.
                    –5
                    то есть вы не можете объяснить каким макаром компилятор сделает такой код просто ссылаетесь на стандарт? :) Хороший подход, правильный :) можете вообще выбросить весь свой код, если вы даже не можете себе представить во что его компилирует компилятор :)
                      +2
                      Мм, вы это серьёзно, да? Вам термин «неопределённое поведение» о чем-нибудь говорит?
                        –3
                        мне то говорит :) но я в отличии от вас ЗНАЮ во что компилируется этот код :) такчто для меня на всех существующих компиляторах именно МОЙ пример вполне себе имеет определенное поведение. Это не f(++i,i++) где реально поведение может отличаться от компилятора к компилятору. И не while( signal_flag ){}; где signal_flag будет лежать в регистре практически с любой опцией оптимизации. Вы прежде чем читать undefined behavior подумали также бы в чем этот undefined состоит… Но видимо поколение нынче такое, вглубь нишагу…
                          +4
                          А я вас узнал — вы прошивки для Фобос-Грунтов пишете, да?
                            –2
                            И про фобос-грунт мимо, хоть бы почитали в чем там причину откопали: использование статичтеской памяти очень чувствительной к радиации да еще и оба дублирующих модуля стояли рядом. Такчто там прошивка не причем. Всетаки это мода текущего поколения, не зная ничего глубоко пытаться рассуждать о том что они «знают».
                              0
                              Окей.

                              1) Вы объявляете глобальную не-volatile переменную для работы с сигналами. Вы хотите проверять её только из what_about_our_signal_variable();

                              2) Эта функция инлайнится в call-site, потому что больше она ниоткуда не вызывается.

                              3) При заходе в call-site функцию переменная кладется в регистр.

                              4) Происходит сигнал, регистры и прочие вещи сохраняются, ваш хендлер инкрементирует переменную в памяти.

                              5) Старое значение в регистре восстанавливается, инкремент вы не заметили, но в памяти при этом будет новое значение.

                              6) Ваша программа начинает вести себя непредсказуемо, потому что вы ввели в неё неопределённое поведение; стандарт в этом случае развязывает руки компилятору, оставляя вас без единого формального способа доказать корректность вашего кода.
                                –1
                                5) ничего страшного не произошло :) мы просто не узнали в тот самый момент времени :) но мы узнаем об изменени на следующей итерации :)) Думаете мне важно узнать о сигнале точь в точь когда он пришел? :) нет мне важно узнать о том что он вообще был :) для этого и используется флаг.

                                6) с чего вы это взяли? :) Читайте пункт 5 :)
                                  –1
                                  Никто не гарантирует, что переменная будет перечитана, она же лежит в регистре и _там_ её никто не менял, так что вы можете никогда не узнать о том, что она была изменена из хендлера.
                                    +1
                                    опять двадцать пять? :) с чего она лежит в регистре то? :) мы из участка проверки давным давно ушли и делаем все из изначального списка по кругу :)
                                      –1
                                      Она лежит в регистре, потому что вы не покидали call-site-а с заинлайнеными одноразовыми функциями, мало понимаете в C++, почти ничего не знаете о компиляторах и любите почесать языком впустую =) Удачи!
                                        +1
                                        обоже :) я покинул call-site c «заинлаенными функциями» читайте внимательно :) такчто мало понимаете с++ и вообще ничего не знаете как исполняется код (соответственно и знание компиляторов на томже уровне). удачи в ваших «познаниях»
            +2
            Это очень хорошее и полезное правило.
            Но оно, конечно, может быть уточнено.
            1) В обработчике сигнала можно выполнять reenterable-операции. К сожалению, к ним практически можно отнести лишь простое присваивание и функции, написанные реентерабельным образом.
            2) Если время приема сигнала аккуратно отслеживать (например, принимать сигналы только в определенные моменты или интервалы времени), то можно безопасно совершать большее число действий.
            3) Из обработчика можно производить возврат в другой контекст (отличный от контекста, сохраненного при получении сигнала).
              0
              В обработчике сигнала можно выполнять reenterable-операции. К сожалению, к ним практически можно отнести лишь простое присваивание и функции, написанные реентерабельным образом.
              А ещё можно вызывать syscall-ы, т. к. их выполнение не может быть прервано сигналом.
              0
              скрывать не буду, мое знание работы обработчика сигналов находится на уровне юзера. Но, что может случиться такого плохого если в обработчике для например SIGUSR1 будет нетривиальная логика?
                +3
                «Нетривиальная логика» — понятие растяжимое. Но если вы например будете выделять память в обработчике сигнала как-то иначе кроме как с помощью mmap, вас ждут большие неприятности. А что если тред как раз выполнял malloc() в момент прихода сигнала? У вас и куча в неконсистентном состоянии, и мьютекс залочен.
                  0
                  Кстати, операции с std::string как раз во многих случаях вызывают malloc. Но только точно ли malloc нереентерабельна? Мне кажется, что она с большой вероятностью реентерабельна, иначе бы тысячи программ сыпались.
                    +1
                    Нет, malloc не реентерабельна. А также масса других функций.
                      +3
                      Вы путаете реентерабельность с thread safety. malloc — thread safe, но не signal-safe.
                +2
                Но мы можем сами определить обработчики для интересующих нас сигналов. К примеру вот такой
                void hdl(int sig)
                {
                    std::string signal;
                    if(sig == SIGUSR1)
                        signal = "SIGUSR1";

                ZOMG! Вы это серьезно?
                  +1
                  нет, это чистой воды провокация и надругательство над Linux way
                    +11
                    Есть ненулевая вероятность, что вашу статью будет читать человек, не знакомый с темой сигналов. Вы создаете ложное впечатления, что в обработчике сигналов можно использовать std::string и std::cout. Делать этого нельзя, но абстрактный Linux way тут совершенно не при чем. Проблема в реентерабельности — доставка сигнала прерывает выполнение потока в произвольный момент времени, поэтому в момент вызова функции-обработчика глобальные объекты, такие как аллокатор динамической памяти (хип) или cout находятся в потенциально неконсистентном состоянии.
                  +2
                  Чтобы не было кода типа:
                  if(sig == SIGUSR1)
                  signal = "SIGUSR1";

                  можно использовать функцию strsignal, которая вернет имя сигнала по его номеру
                    +1
                    Неуклюжий способ, так как в сигналах куча ограничений (многое делать нельзя), и нельзя делать стек обработчиков (так как установка нового отменяет старый). Неудобная система.
                      +3
                      Да, но это простейший способ научить демона общению с внешним миром — например, научиться перезагружать конфигурацию после kill -HUP
                        0
                        Самое главное ограничение — это то, что для выполнения обработчика сигнала система «одалживает» поток приложения, прерывая выполнение в случайной точке. Как следствие — куча ограничений на то, что можно делать в обработчике сигнала. Например Windows в аналогичной ситуации (обработчик Ctrl-C в консольном приложении), просто создает новый поток и выполняет обработчик в нем. Вы только не забывайте о том, что сигналы были изобретены в 1970-х, и никаких потоков еще не было.

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

                        Современные системы кстати позволяют вобще не пользоваться обработчиками сигналов. Нотификации о сигналах можно получать через механизмы eventfd и kqueue.
                        +2
                        Есть мнение, что статья хорошо вылезет в яндексе-гугле; однако ж, в ней есть грубые ошибки.

                        1.SIGKILL нельзя проигнорировать, но можно перехватить и обработать (rtfm sigtap)
                        2. Любой обработчик сигналов должен быть реентерабельным, это особая магия; про volatile rtfm выше.
                          +1
                          s/sigtap/sigtrap/
                            0
                            oops :) букавка потерялась;(
                              0
                              Не-не-не, не сбивайте с толку. man SystemTap.
                              # probe signal.send = _signal.send.*
                              # {
                              # 	sig=$sig
                              # 	sig_name = _signal_name($sig)
                              # 	sig_pid = task_pid(task)
                              # 	pid_name = task_execname(task)
                              # [...]
                              
                              probe signal.send {
                                if (sig_name == "SIGKILL")
                                  printf("%s was sent to %s (pid:%d) by %s uid:%d\n",
                                         sig_name, pi
                              
                              +1
                              >2. Любой обработчик сигналов должен быть реентерабельным

                              Не всегда. Если обработчик установлен с помощью sigaction без флага SA_NODEFER, то реентерабельность можно не обеспечивать, так как сигнал будет заблокирован во время работы обработчика.
                                0
                                Ценное замечание, но с помощью функции sigaction нельзя повесить обработчик на SIGKILL. Если я ошибаюсь приведите пример. У меня не получилось.
                                +4
                                Кстати в статье не рассмотрены несколько важных тем, без которых претендовать на полное освещение вопроса не приходится.

                                — сигналы реального времени;
                                — использование сигналов в системных API для асинхронной нотификации (aio, нотификации готовности файловых дескрипторов (fcntl(F_SETSIG)));
                                — альтернативные способы получения нотификаций о приходе сигналов (signalfd, kqueue).
                                  +2
                                  >PS. Посоветуйте куда залить сорцы, что то мне не очень нравиться на дроббоксе их держать, а вдруг удалю.

                                  github же
                                    0
                                    Вот такой у меня вопрос к хабрсообществу. Данную статью, да и данную тему меня заставило изучить одна насущная проблема. И именно последний пример который я привел.

                                    Представьте ситуацию: есть поток он блокируется на операции чтения. И мне нужно его уметь прервать в любой момент. В WIN это делается с помощью WaitForMultipleObject + Event. Аналог под Linux это только ppoll (pselect). Но приходиться работать с сигналами на уровне треда, что не очень то хорошо. Да и само использование сигналов обычно не приветствуется. Но как тогда сделать задуманное? В особенности если прервать поток нужно не в момент завершение работы, а просто как прервать таску. И таких потоков много, и каждый их них независим, а обработчик сигналов один.
                                    Я было думал написать механизм «обработчик сигнала для треда», но в свете новых фактов я уже сомневаюсь, что это возможно, а если и возможно, то уж точно не так как я себе это представлял.
                                      0
                                      Кто мешает в качестве флага выхода завести витовый вектор по биту на тред, и проверять в треде соответствующий бит?
                                        0
                                        битовый вектор, конечно
                                          0
                                          1. вектор = динамическая память = mmaloc как я понял нельзя использовать в обработчике сигналов.
                                          2. привязываем трет к нужному биту на этапе проектирования? а если не получается?
                                            0
                                            Слово «вектор» я использовал в классическом значении — массив, без привязки к конкретной реализации.
                                            В простейшем случае, когда нитей немного, это может быть простой инт. Если тредов больше, чем разрядность целевой машины, это будет массив.
                                            На класс памяти никаких ограничений на самом деле нет. Конечно, логично разместить его статически, но даже если пришла фантазия разместить его в куче — да пожалуйста, главное сделать это при старте до настройки обработки сигналов.
                                            0
                                            FYI: флаги можно держать в TLS. Pthread_get(_set)specific безопасны для использования в обработчиках сигналов (к сожалению, только de facto, но не de jure.)
                                              0
                                              Зачем усложнять простые вещи. Флаги по определению thread safe. Устанавливаются только в обработчике прерываний, треды его только читают. Обычного статического инта, массива вполне достаточно.
                                                0
                                                ИМХО это вы усложняете. Как именно предполагается сопоставлять pthread_t (а больше в обработчике сигнала ничего не доступно) и индекс элемента в массиве?
                                                  0
                                                  На самом деле в обработчике доступен только номер сигнала.
                                                  Общая схема предполагается такой: при старте нити она получает порядковый номер. Далее управляющая нить решает, что какую-то, скажем третью, нить нужно остановить. Тогда она в стоп-векторе третий бит ставит в один и вызывает сигнал. В обработчике ничего не происходит. Все нити в состоянии чтения выходят из функции с состоянием EINTR. Нити проверяют наличие флага в векторе, третья завершается, остальные перезапускают функцию чтения.
                                                    0
                                                    а если порядок нитей не определен?

                                                    А разве когда пошлется сигнал (наверно SIGHUP) прервутся блокирующие операции по всех нитях? Или только в той которой адресовался сигнал?

                                                    Предложенный вами способ применим, но имеет явные ограничения на архитектуру.
                                                      0
                                                      RTFM
                                          +1
                                          есть поток он блокируется на операции чтения

                                          Я почему-то думал, что в этом случае EINTR должен приходить, нужно только убедиться, что системный вызов не будет перезапущен. К примеру, вот: pastebin.com/KtQ3migv

                                          Если послать SIGHUP, то приложение недемеленно прервется с сообщением «read: interrupted system call», главное сбросить SA_RESTART из флагов при установке обработчика и корректно обрабатывать ситуацию с EAGAIN в коде обработчика.
                                            0
                                            спасибо, долго же я это искал :) Правда я думал как такое сделать без сигналов, ну а потом постепенно пришел к ppoll.
                                            +1
                                            Представьте ситуацию: есть поток он блокируется на операции чтения. И мне нужно его уметь прервать в любой момент. В WIN это делается с помощью WaitForMultipleObject + Event. Аналог под Linux это только ppoll (pselect). Но приходиться работать с сигналами на уровне треда, что не очень то хорошо.

                                            Справедливости ради, надо сказать что есть еще epoll (более современный и эффективный аналог). Можно тупо добавив pipe в список наблюдаемых файловых дескрипторов, и сигналить методом «записать один байт в пайп». В современных линуксах есть eventfd.
                                            0
                                            Годная статья, однако я не поняял что тут нового относительно учебников?



                                            Лично я использую сигналы, для корректного завершения программы по нажатию ctrl-c/z. В особенности это актуально при работе во многопоточном режиме, когда идёт запись на диск и получения данных от сторонних устройств (или сокетов, что не суть важно).

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

                                            atexit(обрабочик_завершения);

                                            А обработчик сигнала выглядит например так:

                                            void sig_stop_all (int signo)
                                            {
                                            exit(EXIT_SUCCESS);
                                            }
                                              0
                                              А зачем освобождать память и закрывать файлы при завершении? Я на это всегда забиваю.
                                                0
                                                На всякий пожарный. Например, если был запущен процесс таким образом:

                                                scriptf = popen(«gnuplot -persist»,«w»);

                                                то он продолжит работать и после завершения программы. Бывает, что сие недопустимо.
                                              0
                                              а какже SIGRT?
                                                0
                                                простите за grammar-nazism, но thread в хорошей переводной литературе принято переводить как «поток», а в случае, где это допускает двоякое толкование (т.е где рядом еще есть «поток данных») — «поток команд». Что, впрочем, заметно и по комментариям.

                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                Самое читаемое