LINUX.ORG.RU

python прерывание работы программы

 ,


2

1

Допустим мы имеем очень примитивный код, вроде:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import signal
import sys
from time import sleep


def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    print(a)
    sys.exit(0)


signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
a = 0
while True:
    a += 1
    sleep(2)
    a += 1
    print(a)
Теперь я хочу в signal_handler сохранять состояние программы, для того чтобы не усложнять пример буду просто печатать a (хотя я не уверен что то как это сделано правильно). Понятно, что состояние когда a - нечётное число некорректно. Т.е. цикл надо досчитывать до конца. Вопрос в том, как это сделать наименее уродски. Да, отлавливать в цикле Ctrl+C нельзя, так как убивать может и мой скрипт на выключение компа, который гарантированно дождётся корректного завершения программы, работающей в фоне.

Конечно можно обмазаться каким-нибудь atomicloop, но боюсь это быдлокод будет в общем случае. Надо понять как это делать правильнее.

★★★★★

Последнее исправление: peregrine (всего исправлений: 4)

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import signal
import sys
from time import sleep


def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    print(a)
    global work
    work = False


signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
a = 0
work = True
while work:
    a += 1
    sleep(2)
    a += 1
    print(a)
Laz ★★★★★
()
import signal
import sys
from time import sleep

class JobDoer:
    def __init__(self):
        self.a = 0

    def do_job(self):
        print('Press Ctrl+C')
        while True:
            self.a += 1
            sleep(2)
            self.a += 1
            print(self.a)

    def finalize(self):
        print('finalizing')
        if self.a % 2 == 1:
            self.a += 1

        print(self.a)
        exit(0)

def get_sighandler(jobdoer):
    def signal_handler(sig, frame):
        jobdoer.finalize()

    return signal_handler

jobdoer = JobDoer()
signal.signal(signal.SIGINT, get_sighandler(jobdoer))
jobdoer.do_job()
moonmadness
()
Ответ на: комментарий от moonmadness

if self.a % 2 == 1:

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

peregrine ★★★★★
() автор топика
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от Laz

Уже лучше, но нет. Потому что print(a) по прежнему выведет нечётное число, хотя дальше оно и досчитает до чётного, а значит этот момент не годится для сохранения состояния приложения.

peregrine ★★★★★
() автор топика
Ответ на: комментарий от peregrine

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

moonmadness
()

signal.siginterrupt(your signal here, False)

эм, оно?

Morin ★★★★
()
Последнее исправление: Morin (всего исправлений: 1)
Ответ на: комментарий от moonmadness

А у нас есть гарантия того, что маскировка успеет произойти? Т.е. не будет того, что сигнал разорвёт точно перед ней и будет каша? Просто я не настолько хорошо понимаю то как работают сигналы в питоне, чтобы быть уверенным в чём-то.

peregrine ★★★★★
() автор топика
Ответ на: комментарий от peregrine

Всё правильно тебе сказали.

В начале программы устанавливаешь обработчик сигнала, когда еще нечего «сохранять».

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

Выходишь из цикла штатным образом, когда внутренние данные корректны. Сохраняешь состояние.

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

wandrien ★★
()
Ответ на: комментарий от peregrine

Уже лучше, но нет. Потому что print(a) по прежнему выведет нечётное число, хотя дальше оно и досчитает до чётного, а значит этот момент не годится для сохранения состояния приложения.

Момент обработки сигнала не годится ни для чего, кроме подъёма флажка о том, что пришло время помереть. Сохранять состояние нужно после выхода из цикла.

Laz ★★★★★
()
Ответ на: комментарий от Laz

Понял уже. Жаль что очень удобно обработку в одном месте не получится сделать. Будет разорванная логика. С учётом всего в треде выходит что-то вроде:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import signal
import os
from time import sleep


class PerfectKiller:
    kill_now = False

    def __init__(self):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)
        self.signal = None

    def exit_gracefully(self, signum, frame):
        self.signal = signum
        self.kill_now = True

    def perfect_exit(self):
        if self.signal is not None:
            signal.signal(self.signal, signal.SIG_DFL)
            os.kill(os.getpid(), self.signal)


print('Press Ctrl+C')
a = 0
killer = PerfectKiller()
while not killer.kill_now:
    a += 1
    sleep(2)
    a += 1
    print(a)
print(f'типо сохранение {a}')
killer.perfect_exit()
Внимание вопрос, особенно к wandrien не налажал ли я где-то тут? Оно вроде нормально работает, но учитывая подводный камень с особенностями «самоубийства процесса» правильным сигналом можно и легко налажать. Люди, даже в ряде проектов на гитхабе делали выход просто через sys.exit с нужным кодом, хотя если верить ссылке это шляпа.

peregrine ★★★★★
() автор топика
Последнее исправление: peregrine (всего исправлений: 1)

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

Все эти пляски с сигналами и пр конечно хороши, но ничего не гарантируют - аппаратный сбой, сбой питания и т.д. и т.п. и привет.

PS оффтопик - я тут сваял быстрый D-мерный медианный фильтр. Вроде ты занимался обработкой изображений, не интересует?;-)

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Вообще лучше заведи гитхаб и выкладывай туда всё интересное. Наверняка кого-то заинтересует, прямо сейчас мне фильтра не нужен, но как я понял у тебя медианный фильтр для N-мерного пространства? Это чтобы с каналами работать как с пространствами? Меня бы он заинтересовал во времена студенчества, потому что я очень хотел писать диплом про работу с картинками, но в результате писал про работу с деньгами.

ЗЫ

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

peregrine ★★★★★
() автор топика
Ответ на: комментарий от peregrine

Вообще лучше заведи гитхаб

У меня есть гитхаб, я его тут просто не показываю;-)

у тебя медианный фильтр для N-мерного пространства?

Да.

Это чтобы с каналами работать как с пространствами?

Э… о таком я не думал, хотя это наверное возможно. Нет, он рассчитан именно на N-мерные картинки. Скажем не из пикселей а из вокселей (хотя 2D тоже могет, и вообще D любое целое положительное число, в разумных пределах конечно;-))

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

Тогда и дамп как таковой не нужен, просто стартуешь из текущего положения помойки?

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Скажем не из пикселей а из вокселей

Может быть полезно в медицине, например для диагностики ковида. Насколько я понимаю КТ/МРТ по слоям картинку делают, так что и в 3D картинку переделать можно.

Тогда и дамп как таковой не нужен, просто стартуешь из текущего положения помойки?

Тоже можно, но захотелось с сигналами повозиться. Тем более, что так можно и журнал перемещений впихнуть и не бояться что что-то улетит не отметившись в нём.

peregrine ★★★★★
() автор топика
Ответ на: комментарий от peregrine

Внимание вопрос, особенно к wandrien не налажал ли я где-то тут?

Внешне всё выглядит ок. Я, правда, вообще не специалист по питону, говнокожу иногда на нём в силу обстоятельств.

Добавлю топик в избранное ради этого code snippet ))

wandrien ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.