logo

Python | Різні способи знищення потоку

Загалом раптове припинення потоків вважається поганою практикою програмування. Раптове припинення потоку може залишити відкритим критично важливий ресурс, який потрібно закрити належним чином. Але ви можете захотіти припинити потік, коли мине певний період часу або буде створено переривання. Існують різні методи, за допомогою яких можна знищити потік у Python.

  • Викликання винятків у потоці python
  • Встановити/скинути позначку зупинки
  • Використання трасування для знищення потоків
  • Використання багатопроцесорного модуля для припинення потоків
  • Знищення потоку Python шляхом встановлення його як демона
  • Використання прихованої функції _stop()

Створення винятків у потоці python:
У цьому методі використовується функція PyThreadState_SetAsyncExc() щоб створити виняток у потоці. Наприклад,



Python3








k алгоритм кластеризації
# Python program raising> # exceptions in a python> # thread> import> threading> import> ctypes> import> time> > class> thread_with_exception(threading.Thread):> >def> __init__(>self>, name):> >threading.Thread.__init__(>self>)> >self>.name>=> name> > >def> run(>self>):> ># target function of the thread class> >try>:> >while> True>:> >print>(>'running '> +> self>.name)> >finally>:> >print>(>'ended'>)> > >def> get_id(>self>):> ># returns id of the respective thread> >if> hasattr>(>self>,>'_thread_id'>):> >return> self>._thread_id> >for> id>, thread>in> threading._active.items():> >if> thread>is> self>:> >return> id> > >def> raise_exception(>self>):> >thread_id>=> self>.get_id()> >res>=> ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,> >ctypes.py_object(SystemExit))> >if> res>>1>:> >ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,>0>)> >print>(>'Exception raise failure'>)> > t1>=> thread_with_exception(>'Thread 1'>)> t1.start()> time.sleep(>2>)> t1.raise_exception()> t1.join()>

>

>

Коли ми запускаємо наведений вище код на машині, і ви помітите, щойно викликається функція raise_exception(), цільова функція run() завершується. Це пояснюється тим, що як тільки виникає виняток, керування програмою виходить із блоку try, а функція run() завершується. Після цього можна викликати функцію join(), щоб припинити потік. За відсутності функції run_exception() цільова функція run() продовжує працювати вічно, а функція join() ніколи не викликається, щоб припинити потік.

Встановити/скинути позначку зупинки:
Щоб закрити потік, ми можемо оголосити прапор зупинки, і цей прапор час від часу перевірятиме потік. Наприклад

Python3




# Python program showing> # how to kill threads> # using set/reset stop> # flag> import> threading> import> time> def> run():> >while> True>:> >print>(>'thread running'>)> >global> stop_threads> >if> stop_threads:> >break> stop_threads>=> False> t1>=> threading.Thread(target>=> run)> t1.start()> time.sleep(>1>)> stop_threads>=> True> t1.join()> print>(>'thread killed'>)>

>

>

У наведеному вище коді, щойно встановлено глобальну змінну stop_threads, цільова функція run() завершується, і потік t1 можна припинити за допомогою t1.join(). Але з певних причин можна утриматися від використання глобальної змінної. Для таких ситуацій можна передати функціональні об’єкти, щоб забезпечити подібну функціональність, як показано нижче.

Python3




# Python program killing> # threads using stop> # flag> import> threading> import> time> def> run(stop):> >while> True>:> >print>(>'thread running'>)> >if> stop():> >break> > def> main():> >stop_threads>=> False> >t1>=> threading.Thread(target>=> run, args>=>(>lambda> : stop_threads, ))> >t1.start()> >time.sleep(>1>)> >stop_threads>=> True> >t1.join()> >print>(>'thread killed'>)> main()>

>

>

Об’єкт функції, переданий у наведеному вище коді, завжди повертає значення локальної змінної stop_threads. Це значення перевіряється функцією run(), і як тільки stop_threads скидається, функція run() завершується, і потік можна припинити.

Використання трасування для знищення потоків:
Цей метод працює шляхом встановлення сліди в кожній нитці. Кожна траса припиняється після виявлення певного стимулу або позначки, таким чином миттєво вбиваючи пов’язаний потік. Наприклад

Python3




# Python program using> # traces to kill threads> import> sys> import> trace> import> threading> import> time> class> thread_with_trace(threading.Thread):> >def> __init__(>self>,>*>args,>*>*>keywords):> >threading.Thread.__init__(>self>,>*>args,>*>*>keywords)> >self>.killed>=> False> >def> start(>self>):> >self>.__run_backup>=> self>.run> >self>.run>=> self>.__run> >threading.Thread.start(>self>)> >def> __run(>self>):> >sys.settrace(>self>.globaltrace)> >self>.__run_backup()> >self>.run>=> self>.__run_backup> >def> globaltrace(>self>, frame, event, arg):> >if> event>=>=> 'call'>:> >return> self>.localtrace> >else>:> >return> None> >def> localtrace(>self>, frame, event, arg):> >if> self>.killed:> >if> event>=>=> 'line'>:> >raise> SystemExit()> >return> self>.localtrace> >def> kill(>self>):> >self>.killed>=> True> def> func():> >while> True>:> >print>(>'thread running'>)> t1>=> thread_with_trace(target>=> func)> t1.start()> time.sleep(>2>)> t1.kill()> t1.join()> if> not> t1.isAlive():> >print>(>'thread killed'>)>

>

>

У цьому коді start() дещо змінено, щоб встановити функцію трасування системи за допомогою settrace() . Функція локального трасування визначена так, що щоразу, коли встановлено прапор анулювання (killed) відповідного потоку, після виконання наступного рядка коду виникає виняток SystemExit, який завершує виконання цільової функції func. Тепер потік можна припинити за допомогою join().

Використання багатопроцесорного модуля для знищення потоків:
Багатопроцесорний модуль Python дозволяє створювати процеси так само, як ви породжуєте потоки за допомогою модуля потоків. Інтерфейс модуля багатопоточності подібний до інтерфейсу модуля потоків. Наприклад, у заданому коді ми створили три потоки (процеси), які рахуються від 1 до 9.

Python3




# Python program creating> # three threads> import> threading> import> time> # counts from 1 to 9> def> func(number):> >for> i>in> range>(>1>,>10>):> >time.sleep(>0.01>)> >print>(>'Thread '> +> str>(number)>+> ': prints '> +> str>(number>*>i))> # creates 3 threads> for> i>in> range>(>0>,>3>):> >thread>=> threading.Thread(target>=>func, args>=>(i,))> >thread.start()>

>

>

Функціональність наведеного вище коду також можна реалізувати за допомогою багатопроцесорного модуля подібним чином, з дуже незначними змінами. Перегляньте наведений нижче код.

Python3




# Python program creating> # thread using multiprocessing> # module> import> multiprocessing> import> time> def> func(number):> >for> i>in> range>(>1>,>10>):> >time.sleep(>0.01>)> >print>(>'Processing '> +> str>(number)>+> ': prints '> +> str>(number>*>i))> for> i>in> range>(>0>,>3>):> >process>=> multiprocessing.Process(target>=>func, args>=>(i,))> >process.start()>

>

>

Хоча інтерфейс двох модулів подібний, обидва модулі мають дуже різні реалізації. Усі потоки спільно використовують глобальні змінні, тоді як процеси повністю відокремлені один від одного. Отже, процеси знищення набагато безпечніші, ніж знищення потоків. Клас Process надає метод, припинити () , щоб припинити процес. Тепер повертаючись до початкової проблеми. Припустимо, у наведеному вище коді ми хочемо вбити всі процеси через 0,03 с. Ця функція досягається за допомогою багатопроцесорного модуля в наступному коді.

Python3




# Python program killing> # a thread using multiprocessing> # module> import> multiprocessing> import> time> def> func(number):> >for> i>in> range>(>1>,>10>):> >time.sleep(>0.01>)> >print>(>'Processing '> +> str>(number)>+> ': prints '> +> str>(number>*>i))> # list of all processes, so that they can be killed afterwards> all_processes>=> []> for> i>in> range>(>0>,>3>):> >process>=> multiprocessing.Process(target>=>func, args>=>(i,))> >process.start()> >all_processes.append(process)> # kill all processes after 0.03s> time.sleep(>0.03>)> for> process>in> all_processes:> >process.terminate()>

>

>

Хоча два модулі мають різні реалізації. Ця функція, надана багатопроцесорним модулем у наведеному вище коді, схожа на знищення потоків. Отже, багатопроцесорний модуль можна використовувати як простий альтернатива кожного разу, коли нам потрібно реалізувати вбивство потоків у Python.

Знищення потоку Python шляхом встановлення його як демона:
Потоки демона це ті потоки, які припиняються під час завершення основної програми. Наприклад

Python3




import> threading> import> time> import> sys> def> func():> >while> True>:> >time.sleep(>0.5>)> >print>(>'Thread alive, and it won't die on program termination'>)> t1>=> threading.Thread(target>=>func)> t1.start()> time.sleep(>2>)> sys.exit()>

>

>

Зауважте, що потік t1 залишається активним і запобігає виходу основної програми через sys.exit(). У Python будь-який активний недемонний потік блокує вихід основної програми. У той час як самі потоки демонів припиняються, як тільки основна програма завершує роботу. Іншими словами, як тільки основна програма завершує роботу, усі потоки демона припиняються. Щоб оголосити потік як демон, ми встановлюємо аргумент ключового слова daemon як True. Наприклад, у наведеному коді демонструється властивість потоків демона.

Python3




# Python program killing> # thread using daemon> import> threading> import> time> import> sys> def> func():> >while> True>:> >time.sleep(>0.5>)> >print>(>'Thread alive, but it will die on program termination'>)> t1>=> threading.Thread(target>=>func)> t1.daemon>=> True> t1.start()> time.sleep(>2>)> sys.exit()>

>

>

Зауважте, що як тільки основна програма завершує роботу, потік t1 припиняється. Цей метод виявляється надзвичайно корисним у випадках, коли завершення програми може бути використано для ініціювання знищення потоків. Зауважте, що в Python головна програма завершує роботу, як тільки всі потоки, що не є демонами, припиняють роботу, незалежно від кількості активних потоків демона. Отже, ресурси, які зберігаються цими потоками демонів, такі як відкриті файли, транзакції бази даних тощо, можуть не звільнятися належним чином. Початковий потік керування в програмі Python не є потоком демона. Примусове знищення потоку не рекомендується, якщо точно не відомо, що це не призведе до витоків або взаємоблокувань.
Використання прихованої функції _stop():
Щоб припинити потік, ми використовуємо приховану функцію _stop(). Ця функція не задокументована, але може зникнути в наступній версії python.

Python3




# Python program killing> # a thread using ._stop()> # function> import> time> import> threading> class> MyThread(threading.Thread):> ># Thread class with a _stop() method.> ># The thread itself has to check> ># regularly for the stopped() condition.> >def> __init__(>self>,>*>args,>*>*>kwargs):> >super>(MyThread,>self>).__init__(>*>args,>*>*>kwargs)> >self>._stop>=> threading.Event()> ># function using _stop function> >def> stop(>self>):> >self>._stop.>set>()> >def> stopped(>self>):> >return> self>._stop.isSet()> >def> run(>self>):> >while> True>:> >if> self>.stopped():> >return> >print>(>'Hello, world!'>)> >time.sleep(>1>)> t1>=> MyThread()> t1.start()> time.sleep(>5>)> t1.stop()> t1.join()>

>

>

Примітка: Наведені вище методи можуть не працювати в тій чи іншій ситуації, оскільки python не надає жодного прямого методу для знищення потоків.