Обратите внимание, что главные потоки выполнения в двух последних сценариях в конце выполняют цикл ожидания, который может заметно снизить производительность в критически важных приложениях. В таких ситуациях достаточно просто добавить в цикл ожидания вызов функции time.sleep, чтобы оформить паузу между проверками и освободить процессор для других заданий: эта функция будет приостанавливать только вызывающий поток выполнения (в данном случае — главный поток). Можно также попробовать добавить вызов функции sleep в функцию, которая выполняется в потоках, чтобы сымитировать выполнение продолжительных операций.
Для единообразия вместо использования глобальной области видимости можно было бы также организовать передачу блокировки в виде аргумента функции, которая выполняется в потоках. В этом случае все потоки выполнения будут ссылаться на один и тот же объект блокировки, потому что все они являются частью одного и того же процесса. Память процесса, занятая объектом, является памятью, совместно используемой потоками независимо от того, как будет получена ссылка на этот объект (через глобальные переменные, через аргументы функции, через атрибуты объектов или каким-либо другим способом).
И еще — чтобы гарантировать освобождение блокировки при выходе потока выполнения из критического блока, можно использовать инструкцию with, как мы делали это в предыдущей главе, чтобы обеспечить закрытие файлов. Менеджер контекста блокировки приобретает блокировку при входе в инструкцию with и освобождает ее при выходе из тела инструкции, независимо от того, возникло исключение или нет. Этот прием позволяет сэкономить одну строку программного кода и дополнительно гарантирует освобождение блокировки в ситуациях, когда возможно появление исключения. Все эти приемы реализованы в примере 5.10, представляющем улучшенную версию нашего сценария с потоками-счетчиками.
Пример 5.10. PP4E\System\Threads\thread-count-wait3.py
объект мьютекса, совместно используемый всеми потоками выполнения, передается функции в виде аргумента; для автоматического приобретения/освобождения блокировки используется менеджер контекста; чтобы избежать излишней нагрузки в цикле ожидания, и для имитации выполнения продолжительных операций добавлен вызов функции sleep
import _thread as thread, time
stdoutmutex = thread.allocate_lock()
numthreads = 5
exitmutexes = [thread.allocate_lock() for i in range(numthreads)]
def counter(myId, count, mutex): # мьютекс передается в аргументе
for i in range(count):
time.sleep(1 / (myId+1)) # различные доли секунды
with mutex: # приобретает/освобождает блокировку: with
print(‘[%s] => %s’ % (myId, i))
exitmutexes[myId].acquire() # глобальный список: сигнал главному потоку
for i in range(numthreads):
thread.start_new_thread(counter, (i, 5, stdoutmutex))
while not all(mutex.locked() for mutex in exitmutexes): time.sleep(0.25) print(‘Main thread exiting.’)
Различные времена ожидания для разных потоков выполнения делают их более независимыми:
C:\…\PP4E\System\Threads> thread-count-wait3.py
[4] => 0
[3] => 0
[2] => 0
[4] => 1
[1] => 0
[3] => 1
[4] => 2
[5] => 1
[6] => 2
[7] => 3
[8] => 4
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 3
[1] => 2
[2] => 4
[0] => 1
[1] => 3
[1] => 4
[0] => 2
[0] => 3
[0] => 4
Main thread exiting.
Конечно, потоки выполнения могут решать гораздо более сложные задачи, чем простой подсчет. Более практичный пример использования глобальных данных мы рассмотрим в разделе «Добавляем пользовательский интерфейс» в главе 13, где они будут играть роль сигналов
главному потоку, управляющему графическим интерфейсом на основе библиотеки tkinter, о завершении дочерним потоком передачи данных по сети, а также в главе 10, в примере реализации модуля threadtools, и в главе 14, в примере приложения PyMailGUI, для отображения результатов отправки электронной почты в графическом интерфейсе (дополнительные указания по этой теме вы найдете в разделе «Графические интерфейсы и потоки выполнения: предварительное знакомство» ниже, в этой главе). Возможность совместного доступа к глобальным данным из потоков выполнения также является основой организации очередей, которые обсуждаются далее в главе, — каждый поток выполнения может извлекать или добавлять данные, используя один и тот же общий объект очереди.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011