Есть ли способ обеспечить атомарность при наличии многопоточной программы с обработчиками сигналов?

Если у меня есть такая программа (в псевдокоде):

mutex_lock;
func() {
    lock(mutex_lock);
    // Some code (long enough to make a
    // race condition if no proper synchronisation
    // is available). We also going to call a signal,
    // say, SIGINT, through (ctrl-c), while we are in
    // the range of locking and unlocking the lock.
    unlock(mutex_lock);
}

sig_handler_func(sig) {
    // Say, we are handling SIGINT (ctrl-c) signal
    // And we need to call func from here too.
    if (sig == SIGINT) {
        func();
    }
}

main() {
    // Calling func from main
    func();
}

тогда возникнет взаимоблокировка при попытке получить блокировку в func, в то время как она уже получена от «основного» вызывающего объекта. Мне было интересно, есть ли способ приостановить обработку сигнала на языке C, в частности, когда в этом примере вход в func и получение блокировки, а также возобновление обработки сигнала И вызов обработчиков при выходе из func.


person falhumai    schedule 11.07.2016    source источник
comment
Взаимодействие между потоками и обработкой сигналов в самом C не определено. Вам нужно быть более конкретным. Является ли ваша система системой POSIX? Пожалуйста, отметьте свой вопрос соответствующим образом.   -  person Jens Gustedt    schedule 11.07.2016


Ответы (3)


Вам нужна pthread_sigmask, многопоточная версия sigprocmask

Вот пример псевдокода:

int
main(void)
{
    sigset_t omask;
    sigset_t nmask;

    // add as many signals as you want to the mask ...
    sigemptyset(&nmask);
    sigaddset(&nmask,SIGINT);

    // [temporarily] block signals
    pthread_sigmask(SIG_BLOCK,&nmask,&omask);

    // call function safely
    func();

    // restore signal mask
    pthread_sigmask(SIG_SETMASK,&omask,NULL);

    // pending signals should occur now ...
}

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

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

Выделить один поток для обработки сигналов и заставить его выполнять sigsetjmp, а обработчик сигналов - siglongjmp.

Или задайте обработчику сигнала изменчивую глобальную переменную (например, signal_occurred), которая отслеживается на базовом уровне.

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

person Craig Estey    schedule 11.07.2016
comment
Я бы настоятельно не рекомендовал siglongjmp()входить в sigjmp_buf, созданный sigsetjmp() другого потока. - person EOF; 11.07.2016
comment
Но после pthread_sigmask(SIG_SETMASK,&omask,NULL) буду ли я по-прежнему вызывать обработчик сигналов для ожидающих сигналов?? или их надо еще раз позвать?? - person falhumai; 11.07.2016
comment
@EOF Конечно. Моя первая строка была о блокировке сигналов во всех потоках, кроме одного [и именно здесь будет выполняться sigsetjmp/siglongjmp]. И я сказал: выделить один поток для обработки сигналов и заставить он выполнять sigsetjmp, а обработчик сигналов — siglongjmp. Лично я предпочитаю [и использовал] изменчивый глобальный [или эквивалентный] подход. - person Craig Estey; 11.07.2016
comment
После того, как сигналы будут разблокированы, они будут происходить так же, как если бы вы никогда их не блокировали. Таким образом, нет необходимости вручную вызывать обработчик сигнала [чего вы все равно не можете сделать, потому что вы не можете настроить специальный контекст обработчика сигнала]. Он будет вызван для вас [особым способом, которым всегда вызываются обработчики сигналов]. Обратите внимание на последнюю строку комментария в примере кода. Что еще [помимо мьютекса] делает func? Я делал подобные вещи для производственных критически важных систем реального времени, и я снова хочу подчеркнуть, что, возможно, есть лучший способ. - person Craig Estey; 11.07.2016

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

Вы также должны сделать маскировку и демаскировку сигнала атомарным:

static  pthread_mutex_t mask_mutex = PTHREAD_MUTEX_INITIALIZER;
sigset_t old_set;
sigset_t new_set;

sigemptyset( &new_set );
sigaddset( &new_set, SIGINT );

pthread_mutex_lock( &mask_mutex );

pthread_sigmask( SIG_BLOCK, &new_mask, &old_mask );

func();

pthread_sigmask( SIG_SETMASK, &old_mask, NULL );

pthread_mutex_unlock( &mask_mutex );

Без блокировки вокруг pthread_sigmask() потоки, вероятно, испортят сигма-маску процесса, поскольку выполнение перекрывается.

person Andrew Henle    schedule 11.07.2016
comment
Что, если сигнал вызывается перед pthread_sigmask( SIG_BLOCK, &new_mask, &old_mask) и после pthread_mutex_lock( &mask_mutex)? Тогда у нас был бы тупик, верно? Что, если мы поменяем порядок операций? - person falhumai; 12.07.2016
comment
@falhumai Нет, потому что это не тот мьютекс. Если сигнал доставляется после того, как мьютекс, защищающий вызовы pthread_sigmask(), заблокирован, поток, который заблокировал этот мьютекс, еще не может удерживать мьютекс в func(). Между двумя вызовами pthread_sigmask() func() де-факто не является обработчиком сигналов и поэтому может вызываться безопасно. Второй мьютекс необходим для защиты вызовов pthread_sigmask() и поддержания согласованной маски сигнала для процесса. - person Andrew Henle; 12.07.2016

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

person gp8    schedule 12.07.2016