Поведение потоков POSIX отличается между HP-UX и Solaris 10

Я переношу многопоточное приложение с HP-UX на Solaris, и пока все в порядке, кроме одного! Приложение имеет поток, который обрабатывает сигналы и, когда некоторые из них получены, выполняет некоторую очистку (логирование, уничтожение дочерних процессов и т. д.).

Я сократил код настолько, насколько это было возможно, чтобы сделать хоть какой-то простой пример, показывающий проблему:

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <synch.h>
#include <iostream>
#include <unistd.h>

using namespace std;

pthread_t       m_signalHandlerThread;
sigset_t        m_signalSet;

void    signalHandler()
{
    while ( true )
    {
        cout << "SigWait..." << endl;
        sigwait( &m_signalSet, &sig );
        cout << "Signal!! : " << sig << endl;

        break;
    }

    cout << "OUT" << endl;
}

void*   signalHandlerThreadFunction( void* arg )
{
   signalHandler();

   return (void*)0;
}


int main()  
{
    sigemptyset( &m_signalSet );
    sigaddset( &m_signalSet, SIGQUIT );             //kill -QUIT
    sigaddset( &m_signalSet, SIGTERM );             //kill
    sigaddset( &m_signalSet, SIGINT );              //ctrl-C
    sigaddset( &m_signalSet, SIGHUP );              //reload config

    if ( pthread_create( &m_signalHandlerThread, NULL, signalHandlerThreadFunction, NULL ) )
    {
        cout << "cannot create signal handler thread, system shut down.\n" << endl;
    }

    int iTimeout = 0;
    while (1) 
    {
        if (iTimeout >= 10)
           break;

        sleep(1);
        iTimeout++;
        cout << "Waiting... " << iTimeout << endl;
    }

    cout << "END" << endl;

    exit (0);
}

Использование командной строки компиляции: Solaris:

CC -m64 -g temp.cpp -D_POSIX_PTHREAD_SEMANTICS -lpthread

HP-UX:

/opt/aCC/bin/aCC +p +DA2.0W -AA -g -z -lpthread -mt -I/usr/include  temp.cpp     

Запуск обоих приложений, поведение (нажатие CTRL+C во время 10-секундного цикла):

HP-UX:

./a.out

SigWait...
Waiting... 1
Waiting... 2
Signal!! : 2   <---- CTRL + C
OUT
Waiting... 3
Waiting... 4   <---- CTRL + C again to terminate

Солярис:

./a.out

SigWait...
Waiting... 1
Waiting... 2   <---- CTRL + C
^C

Любая помощь будет более чем приветствуется, так как я уже рву на себе волосы (осталось немного) :)!

Спасибо!


person JoaoSantos    schedule 20.10.2010    source источник


Ответы (4)


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

Это также лучший вариант, поскольку не определено, какой поток в приложении MT получает сигнал. Любые потоки, у которых не заблокирован сигнал, могут его получить. Если у вас есть 2 потока (а в примере у вас есть два потока), то любой из потоков может получить SIGINT.

Вы можете проверить sigprocmask(), чтобы сообщить ОС, что SIGINT должен быть заблокированным в потоке. Это следует делать для каждого потока, IIRC даже для того, который вызывает sigwait().


Редактировать 1. На самом деле я ошибаюсь насчет того, что "должно выполняться для каждого потока" выше. Новый поток наследует свою сигнальную маску от текущего потока. Я понял, что это не может быть правдой, потому что это привело бы к состоянию гонки: сигнал поступает в то время, когда новый поток создан, но еще не установил свою маску сигнала. Другими словами, достаточно задать маску сигнала в основном потоке.

person Dummy00001    schedule 20.10.2010
comment
Спасибо! Это решило мою проблему, даже во всем приложении. Я просто не понимаю, почему это работает на HP-UX... может быть, в этой реализации потока все потоки получают сигнал? - person JoaoSantos; 20.10.2010
comment
может быть, в этой реализации потока все потоки получают сигнал? приложение получило сигнал, но он может быть обработан в любом случайном потоке, где он может быть обработан. HP-UX мог заметить, что поток в вашем приложении использует sigwait(), в то время как Solaris не беспокоится об этом. Сигналы против потоков лучше всего описать как серую зону, где вы действительно не хотите экспериментировать. Даже POSIX не может полностью описать поведение, поскольку более мелкие детали сильно различаются от одной ОС к другой. - person Dummy00001; 20.10.2010
comment
Ставлю -1 за этот ответ. Этот ответ неверен, поскольку вы не можете сериализовать сигнал из классического обработчика сигналов в любой другой поток или структуру данных. Вам придется использовать некоторую блокировку внутри обработчика сигнала, а это невозможно. В обработчике сигналов вы можете сделать очень немногое, использование блокирующих примитивов не является одним из них. - person wilx; 20.10.2010
comment
@wilx: пожалуйста, прочтите это (прокрутите вниз до список функций, которые можно безопасно использовать из обработчика сигнала). Блокировка - это не единственный вариант: я обычно использую write() на канале, но однажды также использовал sem_post(). - person Dummy00001; 20.10.2010
comment
sem_post() не годится, ИМХО, можно пропустить отдельные сигналы. Труба и write() есть, но тогда вам все равно придется иметь поток, ожидающий сигнала. Использование sigwait() в выделенном потоке намного проще. - person wilx; 20.10.2010
comment
@wilx: Как ты можешь пропускать мероприятия с sem_post??? Это семафор, также известный как атомарный счетчик, явно предназначенный для обеспечения безопасности сигналов. Pipe и write() в большинстве случаев подходят, потому что обычно есть потоки, которые в любом случае выполняют мультиплексирование ввода-вывода - еще один файловый дескриптор никогда не повредит. Они на 100 % охватывают то, как я поступал в бесчисленных приложениях по обработке сигналов МТ. И именно sigwait() при даже слегка неправильном использовании может пропустить события. Например, см. вопрос вверху страницы. - person Dummy00001; 20.10.2010
comment
@wilx: еще один неочевидный момент. В любом случае нужен sigaction() - для дополнительного преимущества SA_RESTART. Если, конечно, вы не планируете постоянно отлаживать все приложение, чтобы сделать обработку EINTR изящной. - person Dummy00001; 20.10.2010
comment
@ Dummy00001: Если вы заблокируете сигналы, вы не получите EINTR. Что касается sem_post(), счетчик ограничен, вы не можете постоянно поднимать его в обработчике. В какой-то момент вы не можете повышать его дальше, и тогда вы начинаете терять сигналы. - person wilx; 21.10.2010
comment
@wilx: хорошее замечание о заблокированных сигналах. Хотя переполнение семафора практически нереально: вы забываете, что очереди сигналов очень и очень ограничены (если вообще доступны), и несколько сигналов все равно теряются, прежде чем они даже достигнут приложения - задолго до того, как семафор может переполниться. - person Dummy00001; 21.10.2010

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

person nos    schedule 20.10.2010


Практически единственный способ хорошо обрабатывать сигналы в многопоточном приложении — это сделать следующее:

  1. Заблокируйте все сигналы в main() заранее, до того, как будут созданы какие-либо другие потоки, используя pthread_sigmask().
  2. Создайте поток обработки сигналов. Используйте sigwait() или sigwaitinfo() для обработки сигналов в простом цикле.

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

person wilx    schedule 20.10.2010