И совместно используемая информация

i sovmestno ispolzuemaya informaciya Системные инструменты параллельного выполнения

В отличие от процессов, потоки выполняются параллельно внутри одного и того же процесса и совместно используют глобальную память. Все потоки в примере 5.18 изменяют одну и ту же глобальную переменную exitstat.

Пример 5.18. PP4E\System\Exits\testexit_thread.py

порождает потоки выполнения и следит за изменениями в глобальной памяти; обычно потоки завершаются при возврате из выполняемой в них функции, но поток может завершиться, вызвав функцию _thread.exit(); функция _thread.exit играет ту же роль, что и функция sys.exit, и возбуждает исключение SystemExit; потоки взаимодействуют через глобальные переменные, по мере надобности блокируемые; ВНИМАНИЕ: на некоторых платформах может потребоваться придать атомарность вызовам функций print/input — из-за совместно используемых потоков ввода- вывода;

import _thread as thread exitstat = 0

def child():

global exitstat # используется глобальная переменная процесса,

exitstat += 1 # совместно используемая всеми потоками

threadid = thread.get_ident()

print(‘Hello from child’, threadid, exitstat) thread.exit()

print(‘never reached’)

def parent(): while True: thread.start_new_thread(child, ()) if input() == ‘q’: break

if __name__ == ‘__main__’: parent()

Ниже показано, как действует этот сценарий в Windows, — в отличие от ветвления процессов, потоки выполнения поддерживаются и в стандартной версии Python для Windows. Все потоки получают разные идентификаторы — они произвольные, но уникальные среди активных потоков, поэтому их можно использовать в качестве ключей словаря для сохранения информации о потоках (на некоторых платформах идентификаторы потоков могут повторно использоваться после их завершения):

C:\\PP4E\System\Exits> python testexit_thread.py

Hello from child 4908 1

Hello from child 4860 2

Hello from child 2752 3

Hello from child 8964 4 q

Обратите внимание, что значение глобальной переменной exitstat в этом сценарии изменяется каждым потоком выполнения, — из-за того, что потоки совместно используют глобальную память процесса. Эта особенность часто используется для организации взаимодействий между потоками. Вместо того чтобы возвращать коды завершения, потоки могут присваивать значения глобальным переменным модуля или модифицировать изменяемые объекты, а для синхронизации доступа к совместно используемым элементам они могут использовать блокировки и очереди, если это необходимо. В данном сценарии также может возникнуть потребность в синхронизации потоков для изменения глобального счетчика, если он когда-либо будет использоваться для решения практических задач. Может потребоваться синхронизировать даже обращения к функциям print и input, если на используемой платформе потоки могут одновременно обращаться к потокам ввода-вывода. В этом простом демонстрационном сценарии мы отказались от использования блокировок, предположив, что потоки не будут обращаться к этим операциям одновременно.

Как мы уже знаем, работа потока завершается нормальным образом и без сообщений, когда происходит возврат из функции, запущенной потоком, и значение, возвращаемое функцией, игнорируется. Кроме того, может быть вызвана функция _thread.exit для завершения вызвавшего ее потока явно и тихо. Эта функция действует почти в точности как sys. exit (но не принимает аргумента с кодом завершения) и возбуждает исключение SystemExit в вызвавшем ее потоке. Поэтому поток можно также досрочно завершить, вызвав функцию sys.exit или непосредственно возбудив исключение SystemExit. Следите, однако, за тем, чтобы не вызвать внутри функции потока функцию os._exit, — это может привести к странным результатам (на моей системе Linux в результате подвешивался весь процесс, а в Windows уничтожались все потоки процесса!).

В альтернативном модуле threading реализация потоков не имеет метода, эквивалентного функции _thread.exit(), но, так как единственное действие, которое выполняет последний, — это возбуждение исключения SystemExit, применение этой операции при использовании модуля threading даст такой же эффект — поток немедленно и тихо завершит работу, как, например, в следующем фрагменте (этот программный код находится в файле testexitthreading.py в дереве примеров):

import threading, sys, time

def action():

sys.exit() # или возбуждение исключения SystemExit() print(‘not reached’)

threading.Thread(target=action).start() time.sleep(2) print(‘Main exit’)

Помните также, что потоки выполнения и процессы имеют собственные модели продолжительности жизни, которые мы исследовали выше. Напомним, что если дочерние потоки продолжают выполняться, то поведение, обеспечиваемое двумя модулями работы с потоками, будет различаться — на большинстве платформ программа завершится, если главный поток был создан с помощью инструментов модуля _thread, но не сможет завершиться, если использовался модуль threading и все дочерние потоки не были запущены, как потоки-демоны. В случае использования процессов является нормальным, когда дочерние процессы «переживают» своего родителя. Эту отличительную черту процессов легко объяснить, если помнить, что потоки выполнения — это всего лишь вызовы функций внутри процесса, а процессы — это более автономные и независимые единицы.

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

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий