Страницы

Поиск по вопросам

Показаны сообщения с ярлыком обработка-сигналов. Показать все сообщения
Показаны сообщения с ярлыком обработка-сигналов. Показать все сообщения

вторник, 31 декабря 2019 г.

Нахождение положительных пиков функции

#python #алгоритм #обработка_сигналов




Требуется программно найти количество и местоположение положительных пиков на графике.
Известно, что количество пиков нечетное. Обычно 3, 5 или 7. В данном случае 3. Также
известно, что за каждым положительным пиком следует отрицательный.
Как лучше решить такую проблему на Python? Желательно без подбора коэффициентов
Дополнение: Данные всегда начинаются с положительного пика и заканчиваются отрицательным.
Данные представляют собой вертикальную проекцию изображения, обработанную функцией
np.gradient(projection)/projection. То есть отношение второй производной функции к
её высоте.
    


Ответы

Ответ 1



Поскольку просто локальный экстремум не устраивает, надо добавить проверку превышения порога для отбраковки мелких пиков и артефактов. Порог можно вычислить как СКО выборки, умноженное на некоторую константу, соответствующую доверительному интервалу для математического ожидания. На практике эту константу принимают равной 2.5. UPD С учётом дополнительных условий задачи пиком следует считать максимум на отрезке от "верха" (начальной точки или первого значения выше q) до "низа" (первого значения ниже -q). При этом для массива размерности n возможен следующий алгоритм: Найти СКО выборки и вычислить порог q. Положить top=0. Найти координату bottom первой точки отрезка [top, n-1] со значением меньше -q. Если такой точки нет, перейти к п.7. Найти координату максимума на отрезке [top, bottom-1]. Если значение максимума больше q, записать его в массив пиков. Найти координату top первой точки отрезка [bottom+1, n-1] со значением больше q. Если такой точки нет, перейти к п.7. Перейти к п.3. Использовать массив пиков.

Ответ 2



Ничего лучшего, чем поиск пиков в массиве в голову не приходит. Строгое чередование положительных и отрицательных пиков облегчает задачу. Т.е. алгоритм примерно такой. Выбираем значения двух порогов: положительного и отрицательного. Ищем максимум пока сигнал больше отрицательного порога. Найденный максимум и есть пик, а индекс в массиве - его позиция. Ждем превышения положительного порога и ищем очередной положительный пик. Пороги подбираются так, чтобы быть выше шума: колебания около нуля. Их значения могут быть ясны исходя из природы сигнала (минимально возможная величина пика), либо можно попытаться их вычислить статистически (найти среднеквадратическое отклонение шума и установить порог в значение, например 3 СКО). Боюсь, что готовой функции в библиотеках не будет. Однако реализация такого алгоритма тривиальна. К сожалению, с Python мало знаком, поэтому приведу возможную реализацию на C++ (думаю, что проблем с пониманием возникнуть не должно): typedef double sample_type; struct Peak { sample_type magnitude; std::size_t pos; }; std::vector peaks(const std::vector &sig) { static sample_type threshold_lo = -0.05; static sample_type threshold_hi = 0.05; std::vector ret_peaks; std::vector::size_type isample = 0; while (1) { while ((isample < sig.size()) && (sig[isample] < threshold_hi)) { ++isample; } if (isample >= sig.size()) break; ret_peaks.push_back(Peak(sig[isample], isample)); while ((isample < sig.size()) && (sig[isample] > threshold_lo)) { if (sig[isample] > ret_peaks.back().magnitude) { ret_peaks.back().magnitude = sig[isample]; ret_peaks.back().pos = isample; } ++isample; } if (isample >= sig.size()) break; } return ret_peaks; } Можно также использовать корреляцию (для ее вычисления должна быть готовая функция в scipy). Но, на мой взгляд, в данном случае это более сложный подход. О корреляции можно также почитать здесь

воскресенье, 22 декабря 2019 г.

Алгоритмы активного шумоподавления

#алгоритм #аудио #fft #обработка_сигналов


Меня интересуют алгоритмы активного шумоподавления. Углубляясь в детали реализации,
выполняется спектральное вычитание "на ходу".

Имеется некоторый профиль шума и небольшой длины запись, из которой этот шум нужно
удалить ("вычесть").

Так у меня вопрос по конкретному алгоритму спектрального вычитания: как оно выполняется
и, собственно, над чем? В общих чертах я себе это представляю как вычитание из спектра
(соответствие частот амплитудам гармоник) сигнала спектра шума и обратное преобразование
Фурье. Вообще, вроде как, применяемое здесь FFT даёт не соответствие частот амплитудам,
а просто массив комплексных чисел. Как тут вычитать что из чего не представляю...

P.S.: Не нужно давать ссылки на курсы по обработке звука и спектральному анализу
сигналов. Просто вопрос - просто ясный ответ.



Есть такие специализированные программы, как, например, Noise Gator. Так вот, если
кому известно, подскажите, какие алгоритмы шумоподавления ими используются (в частности,
используется ли спектральное вычитание; если да, то откуда программа берёт слепок шума).
    


Ответы

Ответ 1



Алгоритмы удаления шума невозможно рассмотреть без углубления в математику, акустику и теорию спектрального анализа сигналов. Любой анализ сигнала с помощью преобразований Фурье предполагает, что сигнал является стационарным на исследуемом отрезке. Поэтому, если сигнал нестационарный, он разбивается на отдельные отрезки, называемые окнами. Выбор размера окна зависит от типа исследуемого сигнала, обычно около 25 - 50 мс для звукового сигнала (меньшие значения - для человеческой речи, большие - для музыки, особенно состоящей из струнных смычковых инструментов). Можно использовать перекрывающиеся окна, для повышения точности анализа. Однако, просто так применить преобразование Фурье к обрезанным окнам нельзя, при этом некорректно обрабатываются граничные области отрезков. Для решения этой проблемы сигнал предварительно умножают на специальную весовую функцию ("оконную"). Примеры оконных функций см. в статье Оконное преобразование Фурье Далее выполняется непосредственно преобразование Фурье. Оно дает в результате спектр сигнала, т.е. значение комплексной амплитуды для различных диапазонов частот. Из него и надо вычитать спектр шума. Из модуля комплексной амплитуды сигнала вычитается модуль комплексной амплитуды шума, умноженный на некий коэффициент; если результат отрицательный, он заменяется на ноль. Фазовый компонент оставляется нетронутым. К результату вычитания можно применить обратное преобразование Фурье, и получить "очищенный" сигнал. Итоговый алгоритм шумоподавления при наличии известного образца шума: Разделение сигнала на окна Применение оконного преобразования Фурье к окнам Вычитание (по модулю) спектра амплитуды шума из спектра амплитуды сигнала: A = Max ( A с. - k * А ш. ; 0) где k - коэффициент, подбираемый опытным путем Применение обратного преобразования Фурье к результату Размер окна, перекрытие окон, тип применяемой оконной функции подбираются опытным путем. Ссылки Removing noise from audio using Fourier transform in Matlab How can I select an optimal window for Short Time Fourier Transform? How to select frequency resolution and window size in FFT?

Ответ 2



Вы уже задавали этот вопрос, а я отвечал. Попытаюсь ответить лучше, чем в прошлый раз. ОКНО Пусть шум это синус (наводка сети электроснабжения, например). Фотографируем фрагмент шума, чтобы потом алгоритмом FFT получить его спектр: Кажется, что это синус, но на самом деле это вот что: Резкий перепад, выделенный красным - это очень сильное искажение. Если вы прослушаете это в наушниках ушами, то поймете, что это совсем не тот шум, который вы хотите вычесть из своего сигнала. Чтобы уменьшить искажение, применяется окно (не ликвидировать, ведь окно само вносит искажения в сигнал): Результат применения окна: Это была работа с образцом шума. Теперь можно выполнить БПФ на этом образце шума и получить его спектр. Дальше, на каждом входном блоке сигнала можно так же выполнить БПФ и получить его спектр. Что получится, если попытаться вычесть на каждой частоте абсолютное значение спектра шума из абсолютного значения спектра сигнала. Получится то, что этой операции в принципе соответствует умножение спектра данного блока сигнала на некоторый другой спектр, то есть применение цифрового фильтра. Только для следующего блока входного сигнала этот фильтр будет уже другим. В результате ситуация будет примерно как на второй картинке - разрывы на границах блоков. Лично я вышел из этой ситуации следующим образом: применял к блоку сигнала сразу оба фильтра и линейно интерполировал результат, то есть в первой точке блока сигнала действовал только первый фильтр, в последней - только второй, и так линейно.

понедельник, 16 декабря 2019 г.

Сигналы и слоты Qt как в них ориентироваться?

#cpp #qt #обработка_сигналов #сигналы_слоты #инструменты_разработчика


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


Ответы

Ответ 1



Какие только возможно связи делать средствами дизайнера, чтобы не забивать ими исходники. Массовое создание связей выносить в отдельный метод. В этом методе группировать создание связей по источникам. Обязательно проверять, что connect возвращает TRUE. Использовать возможности C++11 для connect(), тогда проверка совместимости сигнала и слота будет происходить в момент компиляции. Пример для 5-го пункта: connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue ); Документация

вторник, 10 декабря 2019 г.

Перехват сигналов в Python

#cpp #python #процесс #обработка_сигналов #python_internals


Есть две программы, общающиеся между собой по именованным каналам. Одна на C++, вторая
на Python. Причём первая запускает вторую (стандартным способом, через fork + exec).

Участок коммуникации родительской программы (C++):

// ...
int pipeDescr;
std::string outputPipeName{"inputPipe"};
std::string inputPipeName{"outputPipe"};
char *message = new char[BUFSIZE];
// ...
while (true)
{
    int bytesNumber = 0;
    // ...
    if ((pipeDescr = open(outputPipeName.c_str(), O_WRONLY)) <= 0)
        break;

    bytesNumber = write(pipeDescr, message, strlen(message));
    if (bytesNumber <= 0)
        break;

    close(pipeDescr);

    message[0] = '\0';
    if ((pipeDescr = open(inputPipeName.c_str(), O_RDONLY)) <= 0)
        break;

    bytesNumber = read(pipeDescr, message, BUFSIZE);
    if (bytesNumber <= 0)
        break;

    close(pipeDescr);
    // ...
}


В отдельном потоке работает функция watchDog, которая несёт ответственность за работу
дочернего приложения на Python.

void watchDog(int clientSocket, pid_t pid, bool &stopWatchDog)
{
    while (true)
    {
        // Дочерняя программа завершилась с ошибкой
        if (waitpid(pid, NULL, WNOHANG) != 0)
        {
            // ...
        }

        // Родительская программа закрывается
        mutexClosing.lock();
        if (closing)
        {
            mutexClosing.unlock();
            if (waitpid(pid, NULL, WNOHANG) == 0)
            {
                kill(pid, SIGTERM);
                waitpid(pid, NULL, 0);
            }
            break;
        }
        mutexClosing.unlock();

        // Программное отключение WatchDog-а
        mutexWatchDog.lock();
        if (stopWatchDog)
        {
            if (waitpid(pid, NULL, WNOHANG) == 0)
            {
                kill(pid, SIGTERM);
                waitpid(pid, NULL, 0);
            }
            mutexWatchDog.unlock();
            break;
        }
        mutexWatchDog.unlock();
    }
}


При этом есть три исхода:


падение дочерней программы
завершение родительской программы
завершение дочерней программы без завершения родительской


В последних двух вариантах необходимо плавно завершить дочернюю программу, поэтому,
я отправляю ей сигнал SIGTERM и ожидаю завершения.

Участок коммуникации дочерней программы (Python):

def sigterm_handler(signal, frame):
    print('\nGot sigterm!\n')
    sys.exit(0)

def main():
    input_pipe_name = "inputPipe"
    output_pipe_name = "outputPipe"
    # ...
    signal.signal(signal.SIGTERM, sigterm_handler)
    # ...
    while True:
        pipe_descr = os.open(input_pipe_name, os.O_RDONLY)
        request = os.read(pipe_descr, 10000)
        os.close(pipe_descr)

        reply = work_func(request)

        pipe_descr = os.open(output_pipe_name, os.O_WRONLY)
        os.write(pipe_descr, bytes(reply, 'UTF-8'))
        os.close(pipe_descr)


В код я добавил обработку сигнала SIGTERM. Однако, при передаче этого сигнала, функция
sigterm_handler не вызывается.

Но! Если написать что-то типа этого:

def main():
    signal.signal(signal.SIGTERM, sigterm_handler)

    while True:
        print('waiting...')
        time.sleep(2)


То функция вызовется.

Подскажите, как решить данную проблему!
    


Ответы

Ответ 1



При получении сигнала, обработчик в Си выставляет флаг и сразу же завершается (здесь и далее я описываю СPython реализацию). Обработчик, написанный на Питоне, выполняется только когда контроль возвращается к главному потоку интерпретатора, что происходит позже (например, на следующем байткоде) или никогда. Цитата из официальной документации: A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction) Когда контроль возвращается зависит от того, где выполнение происходит в данный момент (разное поведение возможно для разных функций на разных версиях Питона на разных платформах). Например, os.open() на POSIX системе на Python 3.5, сводится к: do { Py_BEGIN_ALLOW_THREADS fd = open(path->narrow, flags, mode); Py_END_ALLOW_THREADS } while (fd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); Py_BEGIN_ALLOW_THREADS макрос отпускает GIL, что позволяет другим потокам выполнять Питон-код, пока текущий поток блокирован на open(2) системном вызове. При установлении своего обработчика signal.signal() вызов сбрасывает SA_RESTART флаг поэтому системные вызовы такие как open(2) прерываются сигналом и возвращают EINTR, что в данном случае вызывает PyErr_CheckSignals() функцию, которая ничего не делает, если вызов не из главного потока в программе. В главном потоке PyErr_CheckSignals() проверяет был ли сигнал (по флагу, установленному Си обработчиком) и вызывает обработчик, написанный на Питоне. Если обработчик выбросит исключение, то PyErr_CheckSignals() возвращает не ноль и цикл прерывается, что ведёт к возникновению исключения на месте вызова os.open в Питон коде, когда os.open был вызван из главного потока (иначе PyErr_CheckSignals() возвращает 0). В других случаях возможно много вариантов: отпущен/не отпущен GIL (в Си коде), прерывается/автоматически перезапускается ли сам блокирующий вызов (от платформы, Си библиотеки, версии Питона может зависеть), вызывается ли PyErr_CheckSignals() в главном потоке, не сбрасывается ли где-то флаг, что сигнал произошёл до вызова обработчика (signal(SIGINT, custom_handler) не работает на Windows на Python 2.7). Если вы не можете изменить ваш блокирующий Си код, чтобы он PyErr_CheckSignals() вызывал, как это делает os.open при получении сигнала, то чтобы обойти это: вызывайте блокирующий код в фоновом потоке, а в главном потоке, спите с небольшим интервалом (это не спасёт, если ваше Си расширение для CPython не отпускает GIL, как это к примеру re модуль может делать): import threading background_thread = threading.Thread(target=fifo_loop) background_thread.daemon = True background_thread.start() while background_thread.is_alive(): background_thread.join(1) # здесь никакого другого кода, это весь цикл Обратите внимание, что это отличается от предложения добавить time.sleep(1) в ваш цикл, который с FIFO(7) работает. Если сигнал произойдёт вне вызова time.sleep(1) в вашем fifo цикле, то проблема так и останется. Обходное решение работает, потому что цикл с fifo исполняется в фоновом потоке, а главный поток только спит с перерывами. В Питоне 3, можно background_thread.join() использовать без timeout (в Питоне 2, этот .join() не прерывался сигналами).

Ответ 2



после while True: необходимо добавить time.sleep(1), так как нужно немного ждать, чтобы выдать сигнал для процесса. p.s. взято из комментариев

пятница, 5 июля 2019 г.

Функция вызывается с неправильными значениями переменных

Суть вопроса такова. Есть такой код:
#include "stdafx.h"
#define PI 3.1415926535897932384626433832795 #define FR 3 #define SAMPLES 1024
int _tmain(int argc, _TCHAR* argv[]) { base signal[SAMPLES];
for (int i = 0; i < SAMPLES; ++i) { signal[i] = (sin(2*PI*i*FR), 0); }
FFTCalculate(signal, SAMPLES, false);
FILE* f_pointer = fopen("fft_result.txt", "w");
for (int i = 0; i < SAMPLES; ++i) { char num = (char)signal[i].real(); int out = fprintf(f_pointer, &num); } int out = fclose(f_pointer); return 0; }
в нем вызывается функция FFTCalculate(signal, SAMPLES, false). Ниже приведен код функции:
void FFTCalculate(base signal[], int n, bool invert) { int log_N; double x = frexp((double)n, &log_N);
calc_rev(n, log_N);
for(int i = 0; i < n; ++i) if (i < rev[i]) swap(signal[i], signal[rev[i]]);
for(int len = 2; len <= n; len<<=1) { double ang = 2*PI/len * (invert?-1:1); int len2 = len>>1;
base wlen (cos(ang), sin(ang)); wlen_pw[0] = base(1, 0); for (int i=0; i for (int i=0; i for(; pu != pu_end; ++pu, ++pv, ++pu){ t = *pv * *pw; *pv = *pu - t; *pu += t; } } } if(invert) for (int i=0; iПри запуске программы появляется ошибка, связанная с выходом за пределы массива. Начал дебажить и выяснил, что когда вызывается функция FFTCalculate(signal, SAMPLES, false), то ей почему то передаются не signal, SAMPLES = 1024 и false, а sugnal, 1245452 и true см. скриншот ниже. В чем дело?


Ответ

Я проверил на GCC 4.8.2 под linux. Параметры FFTCalculate передаются корректно. Думаю что, то поведение которое ты видишь в Visual Studio является unexpected behavior. Твоя программа под линуском сразу упала с seg fault.
Ниже пример кода с ошибкой. Ты не проверяешь значения rev[i], что оно может быть больше n (1024).
calc_rev(n, log_N);
for(int i = 0; i < n; ++i) if (i < rev[i]) swap(signal[i], signal[rev[i]]);
Вот что у меня вывелось для первых десяти итераций из массива rev: 0 1024 1536 1280 1792 1152 1664 1408 1920
Надеюсь помог.

вторник, 25 декабря 2018 г.

Нахождение положительных пиков функции


Требуется программно найти количество и местоположение положительных пиков на графике. Известно, что количество пиков нечетное. Обычно 3, 5 или 7. В данном случае 3. Также известно, что за каждым положительным пиком следует отрицательный. Как лучше решить такую проблему на Python? Желательно без подбора коэффициентов Дополнение: Данные всегда начинаются с положительного пика и заканчиваются отрицательным. Данные представляют собой вертикальную проекцию изображения, обработанную функцией np.gradient(projection)/projection. То есть отношение второй производной функции к её высоте.


Ответ

Поскольку просто локальный экстремум не устраивает, надо добавить проверку превышения порога для отбраковки мелких пиков и артефактов. Порог можно вычислить как СКО выборки, умноженное на некоторую константу, соответствующую доверительному интервалу для математического ожидания. На практике эту константу принимают равной 2.5
UPD С учётом дополнительных условий задачи пиком следует считать максимум на отрезке от "верха" (начальной точки или первого значения выше q) до "низа" (первого значения ниже -q). При этом для массива размерности n возможен следующий алгоритм:
Найти СКО выборки и вычислить порог q Положить top=0 Найти координату bottom первой точки отрезка [top, n-1] со значением меньше -q. Если такой точки нет, перейти к п.7. Найти координату максимума на отрезке [top, bottom-1]. Если значение максимума больше q, записать его в массив пиков. Найти координату top первой точки отрезка [bottom+1, n-1] со значением больше q. Если такой точки нет, перейти к п.7. Перейти к п.3. Использовать массив пиков.

вторник, 13 ноября 2018 г.

Алгоритмы активного шумоподавления

Меня интересуют алгоритмы активного шумоподавления. Углубляясь в детали реализации, выполняется спектральное вычитание "на ходу".
Имеется некоторый профиль шума и небольшой длины запись, из которой этот шум нужно удалить ("вычесть").
Так у меня вопрос по конкретному алгоритму спектрального вычитания: как оно выполняется и, собственно, над чем? В общих чертах я себе это представляю как вычитание из спектра (соответствие частот амплитудам гармоник) сигнала спектра шума и обратное преобразование Фурье. Вообще, вроде как, применяемое здесь FFT даёт не соответствие частот амплитудам, а просто массив комплексных чисел. Как тут вычитать что из чего не представляю...
P.S.: Не нужно давать ссылки на курсы по обработке звука и спектральному анализу сигналов. Просто вопрос - просто ясный ответ.

Есть такие специализированные программы, как, например, Noise Gator. Так вот, если кому известно, подскажите, какие алгоритмы шумоподавления ими используются (в частности, используется ли спектральное вычитание; если да, то откуда программа берёт слепок шума).


Ответ

Алгоритмы удаления шума невозможно рассмотреть без углубления в математику, акустику и теорию спектрального анализа сигналов.
Любой анализ сигнала с помощью преобразований Фурье предполагает, что сигнал является стационарным на исследуемом отрезке. Поэтому, если сигнал нестационарный, он разбивается на отдельные отрезки, называемые окнами. Выбор размера окна зависит от типа исследуемого сигнала, обычно около 25 - 50 мс для звукового сигнала (меньшие значения - для человеческой речи, большие - для музыки, особенно состоящей из струнных смычковых инструментов). Можно использовать перекрывающиеся окна, для повышения точности анализа.
Однако, просто так применить преобразование Фурье к обрезанным окнам нельзя, при этом некорректно обрабатываются граничные области отрезков. Для решения этой проблемы сигнал предварительно умножают на специальную весовую функцию ("оконную"). Примеры оконных функций см. в статье Оконное преобразование Фурье
Далее выполняется непосредственно преобразование Фурье. Оно дает в результате спектр сигнала, т.е. значение комплексной амплитуды для различных диапазонов частот. Из него и надо вычитать спектр шума. Из модуля комплексной амплитуды сигнала вычитается модуль комплексной амплитуды шума, умноженный на некий коэффициент; если результат отрицательный, он заменяется на ноль. Фазовый компонент оставляется нетронутым. К результату вычитания можно применить обратное преобразование Фурье, и получить "очищенный" сигнал.
Итоговый алгоритм шумоподавления при наличии известного образца шума:
Разделение сигнала на окна Применение оконного преобразования Фурье к окнам Вычитание (по модулю) спектра амплитуды шума из спектра амплитуды сигнала:
A = Max ( A с. - k * А ш. ; 0)
где k - коэффициент, подбираемый опытным путем
Применение обратного преобразования Фурье к результату
Размер окна, перекрытие окон, тип применяемой оконной функции подбираются опытным путем.
Ссылки
Removing noise from audio using Fourier transform in Matlab
How can I select an optimal window for Short Time Fourier Transform?
How to select frequency resolution and window size in FFT?

суббота, 27 октября 2018 г.

Сигналы и слоты Qt как в них ориентироваться?

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


Ответ

Какие только возможно связи делать средствами дизайнера, чтобы не забивать ими исходники. Массовое создание связей выносить в отдельный метод. В этом методе группировать создание связей по источникам. Обязательно проверять, что connect возвращает TRUE Использовать возможности C++11 для connect(), тогда проверка совместимости сигнала и слота будет происходить в момент компиляции.
Пример для 5-го пункта:
connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue );
Документация

понедельник, 15 октября 2018 г.

Перехват сигналов в Python

Есть две программы, общающиеся между собой по именованным каналам. Одна на C++, вторая на Python. Причём первая запускает вторую (стандартным способом, через fork + exec).
Участок коммуникации родительской программы (C++):
// ... int pipeDescr; std::string outputPipeName{"inputPipe"}; std::string inputPipeName{"outputPipe"}; char *message = new char[BUFSIZE]; // ... while (true) { int bytesNumber = 0; // ... if ((pipeDescr = open(outputPipeName.c_str(), O_WRONLY)) <= 0) break;
bytesNumber = write(pipeDescr, message, strlen(message)); if (bytesNumber <= 0) break;
close(pipeDescr);
message[0] = '\0'; if ((pipeDescr = open(inputPipeName.c_str(), O_RDONLY)) <= 0) break;
bytesNumber = read(pipeDescr, message, BUFSIZE); if (bytesNumber <= 0) break;
close(pipeDescr); // ... }
В отдельном потоке работает функция watchDog, которая несёт ответственность за работу дочернего приложения на Python.
void watchDog(int clientSocket, pid_t pid, bool &stopWatchDog) { while (true) { // Дочерняя программа завершилась с ошибкой if (waitpid(pid, NULL, WNOHANG) != 0) { // ... }
// Родительская программа закрывается mutexClosing.lock(); if (closing) { mutexClosing.unlock(); if (waitpid(pid, NULL, WNOHANG) == 0) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } break; } mutexClosing.unlock();
// Программное отключение WatchDog-а mutexWatchDog.lock(); if (stopWatchDog) { if (waitpid(pid, NULL, WNOHANG) == 0) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } mutexWatchDog.unlock(); break; } mutexWatchDog.unlock(); } }
При этом есть три исхода:
падение дочерней программы завершение родительской программы завершение дочерней программы без завершения родительской
В последних двух вариантах необходимо плавно завершить дочернюю программу, поэтому, я отправляю ей сигнал SIGTERM и ожидаю завершения.
Участок коммуникации дочерней программы (Python):
def sigterm_handler(signal, frame): print('
Got sigterm!
') sys.exit(0)
def main(): input_pipe_name = "inputPipe" output_pipe_name = "outputPipe" # ... signal.signal(signal.SIGTERM, sigterm_handler) # ... while True: pipe_descr = os.open(input_pipe_name, os.O_RDONLY) request = os.read(pipe_descr, 10000) os.close(pipe_descr)
reply = work_func(request)
pipe_descr = os.open(output_pipe_name, os.O_WRONLY) os.write(pipe_descr, bytes(reply, 'UTF-8')) os.close(pipe_descr)
В код я добавил обработку сигнала SIGTERM. Однако, при передаче этого сигнала, функция sigterm_handler не вызывается.
Но! Если написать что-то типа этого:
def main(): signal.signal(signal.SIGTERM, sigterm_handler)
while True: print('waiting...') time.sleep(2)
То функция вызовется.
Подскажите, как решить данную проблему!


Ответ

При получении сигнала, обработчик в Си выставляет флаг и сразу же завершается (здесь и далее я описываю СPython реализацию). Обработчик, написанный на Питоне, выполняется только когда контроль возвращается к главному потоку интерпретатора, что происходит позже (например, на следующем байткоде) или никогда. Цитата из официальной документации
A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction)
Когда контроль возвращается зависит от того, где выполнение происходит в данный момент (разное поведение возможно для разных функций на разных версиях Питона на разных платформах). Например, os.open() на POSIX системе на Python 3.5, сводится к
do { Py_BEGIN_ALLOW_THREADS fd = open(path->narrow, flags, mode); Py_END_ALLOW_THREADS } while (fd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
Py_BEGIN_ALLOW_THREADS макрос отпускает GIL, что позволяет другим потокам выполнять Питон-код, пока текущий поток блокирован на open(2) системном вызове.
При установлении своего обработчика signal.signal() вызов сбрасывает SA_RESTART флаг поэтому системные вызовы такие как open(2) прерываются сигналом и возвращают EINTR, что в данном случае вызывает PyErr_CheckSignals() функцию, которая ничего не делает, если вызов не из главного потока в программе. В главном потоке PyErr_CheckSignals() проверяет был ли сигнал (по флагу, установленному Си обработчиком) и вызывает обработчик, написанный на Питоне. Если обработчик выбросит исключение, то PyErr_CheckSignals() возвращает не ноль и цикл прерывается, что ведёт к возникновению исключения на месте вызова os.open в Питон коде, когда os.open был вызван из главного потока (иначе PyErr_CheckSignals() возвращает 0).
В других случаях возможно много вариантов: отпущен/не отпущен GIL (в Си коде), прерывается/автоматически перезапускается ли сам блокирующий вызов (от платформы, Си библиотеки, версии Питона может зависеть), вызывается ли PyErr_CheckSignals() в главном потоке, не сбрасывается ли где-то флаг, что сигнал произошёл до вызова обработчика (signal(SIGINT, custom_handler) не работает на Windows на Python 2.7).
Если вы не можете изменить ваш блокирующий Си код, чтобы он PyErr_CheckSignals() вызывал, как это делает os.open при получении сигнала, то чтобы обойти это: вызывайте блокирующий код в фоновом потоке, а в главном потоке, спите с небольшим интервалом (это не спасёт, если ваше Си расширение для CPython не отпускает GIL, как это к примеру re модуль может делать):
import threading
background_thread = threading.Thread(target=fifo_loop) background_thread.daemon = True background_thread.start() while background_thread.is_alive(): background_thread.join(1) # здесь никакого другого кода, это весь цикл
Обратите внимание, что это отличается от предложения добавить time.sleep(1) в ваш цикл, который с FIFO(7) работает. Если сигнал произойдёт вне вызова time.sleep(1) в вашем fifo цикле, то проблема так и останется.
Обходное решение работает, потому что цикл с fifo исполняется в фоновом потоке, а главный поток только спит с перерывами. В Питоне 3, можно background_thread.join() использовать без timeout (в Питоне 2, этот .join() не прерывался сигналами).