#python #multithreading #python-multithreading
#python #многопоточность #python-многопоточность
Вопрос:
Я только начинаю с Python и наткнулся на локальную память потоков. Я написал небольшую программу, которая использует потоки:
#!/usr/bin/env python3
import logging
import signal
import threading
import time
class WorkerThread(threading.Thread):
def __init__(self, idx):
threading.Thread.__init__(self)
self.thread_index = idx
self.thread_alive = True
def run(self):
logging.info(f'Thread {self.thread_index} is starting up!')
while self.thread_alive:
logging.info(f'Thread {self.thread_index} is still running.')
time.sleep(1)
logging.info(f'Thread {self.thread_index} is stopping!')
def kill(self):
self.thread_alive = False
def main():
logging.basicConfig(format = '%(levelname)s: %(message)s', level = logging.INFO)
def signal_handler(sig, frame):
logging.info('Ctrl c pressed, killing threads and shutting down ...')
nonlocal threads
for thread in threads:
thread.kill()
signal.signal(signal.SIGINT, signal_handler)
logging.info('Signal handler registered, starting threads ...')
threads = []
for i in range(0, 3):
thread = WorkerThread(i)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
signal.signal(signal.SIGINT, signal.SIG_DFL)
if __name__ == '__main__':
main()
Эта программа работает должным образом и выводит что-то вроде:
> python3 main.py
INFO: Signal handler registered, starting threads ...
INFO: Thread 0 is starting up!
INFO: Thread 0 is still running.
INFO: Thread 1 is starting up!
INFO: Thread 2 is starting up!
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
INFO: Thread 2 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 1 is still running.
INFO: Thread 0 is still running.
INFO: Thread 1 is still running.
INFO: Thread 2 is still running.
INFO: Thread 0 is still running.
^CINFO: Ctrl c pressed, killing threads and shutting down ...
INFO: Thread 2 is stopping!
INFO: Thread 1 is stopping!
INFO: Thread 0 is stopping!
В этом случае thread_index
thread_alive
переменные and специфичны для каждого потока, поскольку они специфичны для каждого объекта. Но есть также threading.local()
функция, которая создает локальную память потока. Итак, я попытался использовать это, поскольку я хочу, чтобы мои переменные зависели от потока. Я использовал его после определения класса:
# imports and shebang
class WorkerThread(threading.Thread):
thread_index = threading.local()
thread_alive = threading.local()
# everything else stays the same
Но использование этого ничего не меняет, результат остается прежним. Итак, мои вопросы:
- является ли эта локальная память потока для другого варианта использования или первая программа работала только случайно?
- для чего нужны варианты использования
threading.local()
, поскольку создание объектно-ориентированных (нестатических) переменных, похоже, тоже работает?
Ответ №1:
threading.local()
предназначен для случаев, когда вы не можете или не хотите изменять классы, реализующие потоки.
В приведенном выше примере вы полностью контролируете созданные WorkerThread
и запущенные потоки. Итак, вы знаете, что у вас есть экземпляр для каждого запущенного потока, и вы можете хранить значения в экземпляре, который привязан к потоку. Вот почему ваш первоначальный пример сработал. В этом отношении он работает правильно.
Но не всегда так, что вы управляете потоками. Иногда потоки запускаются библиотекой или фреймворком, и вы предоставляете только некоторый код, который будет выполняться в этих потоках. В этом случае вы не можете изменять Thread
классы и добавлять к ним переменные, зависящие от потока.
Давайте рассмотрим пример многопоточного веб-сервера. Вы предоставляете функции, которые должны обрабатывать входящие запросы. Вы не создаете всю инфраструктуру для прослушивания сокета, анализа http-запроса и т. Д. Все эти действия обрабатываются фреймворком. Для вас запускается пул потоков, и при поступлении входящего запроса фреймворк анализирует его и вызывает обработчик, который вы предоставили, используя поток из пула.
В этом случае давайте представим, что вы хотите сохранить некоторый контекст для обрабатываемого запроса (например, для текущего пользователя, вошедшего в систему), чтобы вы могли получить к нему доступ во время обработки запроса, но не нужно явно передавать его в каждой функции. Вы не можете добавить эту currentUser
переменную в класс потоков, поскольку у вас нет контроля над ней. Но вы можете использовать threading.local()
для ее хранения. И запросы, которые одновременно обрабатываются в нескольких потоках, будут иметь свои собственные копии этого.
То же самое применимо для ваших собственных творений. Когда программа становится более сложной, и вам нужно отделить код инфраструктуры (управление потоками) от логики вашего приложения, может случиться так, что вы не захотите добавлять переменные, специфичные для потоков, в классы потоков и использовать threading.local()
вместо этого.
Ответ №2:
Личное мнение: я бы никогда не использовал локальное хранилище потоков в новом коде.
IMO, лучшее использование для локального хранилища потоков — это когда у вас есть старый модуль, который не зависит от потоков, и он использует глобальные переменные, и вы хотите иметь возможность вызывать старый модуль из нескольких потоков в какой-то новой программе. В этом случае простая замена всех глобальных переменных в старом модуле на локальные потоки может быть дешевым способом позволить каждому потоку в новой программе эффективно иметь свою собственную «копию» модуля.
Но, если бы я писал новый модуль, который будет использоваться в многопоточной среде, тогда в модуле не было бы глобальных переменных. Все это состояние будет переменными-членами некоторого класса (или классов), и каждый вызывающий поток может создавать свои собственные экземпляры класса (классов).