Различно поведение на нишките на 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() като начин да кажете на OS, че 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() не може да се използва, IMHO, можете да пропуснете отделни сигнали. Pipe и write() е, но тогава все още трябва да имате нишка, която чака сигнала. Използването на sigwait() в специална нишка е много по-лесно. - person wilx; 20.10.2010
comment
@wilx: Как можеш да пропуснеш събития с sem_post??? Това е семафор, известен още като атомен брояч - изрично упълномощен да бъде безопасен за сигнала. Pipe и write() през повечето време са добре, защото обикновено има нишки, които така или иначе правят IO мултиплексиране - още един файлов дескриптор никога не вреди. Двете покриват 100% от начина, по който се справих в безброй MT приложения за обработка на сигнали. И точно 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

Не е уточнено коя от вашите 2 нишки ще обработва SIGINT. Ако имате нужда само от една от вашите нишки, за да обработите сигнала, трябва да блокирате този сигнал във всички останали нишки, които имате.

person nos    schedule 20.10.2010


Единственият начин да се справят добре със сигналите в многопоточно приложение е да се направи следното:

  1. Блокирайте всички сигнали в main() рано, преди да бъдат създадени други нишки, като използвате pthread_sigmask().
  2. Създайте нишка за обработка на сигнали. Използвайте sigwait() или sigwaitinfo() за обработка на сигналите в обикновен цикъл.

По този начин нито една нишка освен тази, предназначена за обработка на сигнали, няма да получи сигналите. Също така, тъй като доставката на сигнала е синхронна по този начин, можете да използвате всички комуникационни средства за междунишки, които имате, за разлика от вътрешните класически манипулатори на сигнали.

person wilx    schedule 20.10.2010