Темы - это способ запуска кода одновременно. Это означает, что вы можете запустить функцию A, но до ее завершения вы можете запустить функцию B, и они продолжают выполняться, не обязательно оба одновременно (это параллельное программирование). Подумайте, как в данный момент времени, если вы остановите время и посмотрите, что происходит в вашем коде, вы увидите, что функция A выполнила некоторые инструкции, которые еще не закончены, но некоторые операторы функции B также были выполнены.
В потоковом модуле Python каждый поток является функцией (или методом) с некоторыми возможными аргументами и при запуске тела функции запускается. Когда функция возвращается, поток выполняется, а состояние потока изменяется на not alive
(см. threading.Thread.is_alive()
).
Таким образом, мы создаем поток, передавая функцию/метод ссылки и возможные аргументы:
from threading import Thread
simple_thread = Thread(target=func_with_no_args)
thread_with_args = Thread(target=my_function, args=(arg1, arg2))
И тогда мы начинаем нить, чтобы запустить тело функции.
simple_thread.start()
thread_with_args.start()
Но ток коды продолжается (текущий поток, всегда есть основной поток в программе, которая начинает выполнять его). Поэтому нам не нужно ждать функции (func_with_no_args
или my_function
), чтобы закончить.
И если нам нужно проверить, если функция, которую мы выполняется в потоке закончена, мы можем проверить, если он жив:
simple_thread.is_alive() # return bool
И если мы хотим, чтобы ждать, пока эта функция в потоке не закончил работу, мы можем join
нить. Также хорошо до join
потоки, когда они закончены.
simple_thread.join()
В основном это полезно для задач ввода-вывода, поскольку в то время как программа ожидает данные, чтобы быть готовыми к/от IO он может делать другие вещи.
Я пытаюсь создать поток, который принимает 3 переменные (с плавающей точкой), которые ...
Я предполагаю, что вы хотите достичь чего-то, и вы используете темы в качестве инструмента. Я имею в виду, что создание потоков не является целью здесь, а способ достижения желаемого. Поскольку детали приложения отсутствуют в этом вопросе, я не могу предоставить точный код/образцы, поэтому я собираюсь перейти с абстрактным/общим ответом, надеясь, что вы можете применить эту идею к своему приложению.
Я думаю, что приложение получает данные откуда-то (следовательно, IO), и каждые 30 мс новые данные должны быть отправлены в ardunio (который снова действует как IO). Таким образом, приложение имеет 2 IO-плеча, один для приема данных, а другой - для отправки в ardunio. Поэтому использование потоков может быть оправдано.
У нас есть 2 функции, 1 для чтения данных и 1 для обновления ardunio. Мы запускаем их в 2 потоках, thread_read
(для чтения данных) и thread_ardunio
(для обновления ardunio).
Если это так, нам нужен способ обмена информацией между потоками. Темы облегчают использование памяти (в памяти процесса, доступной через переменные), поэтому мы можем использовать переменную, доступную обеими функциями. Когда переменная обновляется на 1 поток, в другом потоке также будут отображаться обновленные результаты.
storage = None
def read_data():
global storage
# read data from DB, web API, user input, etc.
# maybe in a loop to keep receiving data. Or generate the data from here
storage = (cX, cY, angle)
def send_to_ardunio():
global storage
# send storage data to ardunio, maybe in a loop
# sleeping 30ms after each update
thread_read = Thread(target=read_data)
thread_ardunio = Thread(target=send_to_ardunio)
thread_read.start()
thread_ardunio.start()
Это один из способов пойти. ИМХО не так красиво, так как там есть глобальная переменная. Что мы можем сделать, чтобы устранить переменную? Мы можем использовать очередь (см. queue.Queue
).
Очереди - хорошие способы общения между потоками, я думаю. Таким образом, поток, содержащий данные, помещает их в очередь (производитель a.k.a), а другой поток выбирает элементы из очереди (пользователь a.k.a).
Что-то вроде этого:
def read_data(queue_):
# read data from DB, web API, user input, etc.
# maybe in a loop to keep receiving data, or generate the data from here
data = (cX, cY, angle)
queue_.put(data)
def send_to_ardunio(queue_):
# send data to ardunio, maybe in a loop
# sleeping 30ms after each update
data = queue_.get()
cX, cY, angle = data
queue_ = Queue() # this will be used to transfer data
thread_read = Thread(target=read_data, args=(queue_,))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_,))
thread_read.start()
thread_ardunio.start()
выглядит лучше.
Теперь нам нужно дождаться выполнения функций. Поэтому мы можем вызвать метод join
для потоков. Также на этот раз я взял на себя смелость предположить, что мы можем контролировать, сколько времени потребуется для чтения данных. Если мы должны обновлять ardunio каждые 30 мс новыми данными, то производитель может настроить частоту, и потребитель может просто потреблять без колебаний.
Также нам нужен способ рассказать о потоках, чтобы прекратить производство/потребление. Мы можем использовать Event
(см. threading.Event
) для этого, или для простоты, по согласованию, данные в очереди будут представлять, что потребитель должен остановиться.
def read_data(queue_):
while True:
# calculate/get cX, cY, angle
queue_.put((cX, cY, angle))
# sleep 30ms
# when we finished producing data, put something to the queue to tell the consumer there is no more data. I'll assume None is good option here
queue_.put(None)
def send_to_ardunio(queue_):
while True:
data = queue_.get()
if data is None:
break
cX, cY, angle = data
# update ardunio, because the data is updated every 30ms on the producer, we don't need to do anything special. We can just wait when the data is ready, we'll update.
queue_ = Queue()
thread_read = Thread(target=read_data, args=(queue_,))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_,))
thread_read.start()
thread_ardunio.start()
thread_read.join()
thread_ardunio.join()
Код выше предполагает, что производитель (thread_read
) будет знать, чтобы остановить производство данных.
Если это не так, мы можем использовать Event
для запуска обеих функций, чтобы прекратить производство и потребление.
В конце концов, я столкнулся с небольшим уловом при присоединении потоков. Если основной поток соединяется с другими потоками, он заблокирован и не будет хорошо реагировать на SIGINT. Поэтому, если вы попытаетесь остановить процесс Python (нажав Ctrl + C или отправив SIGINT), он не уйдет.
Однако мы можем попытаться присоединиться к потокам с таймаутом. Поэтому каждый часто основной поток может смотреть на принятый сигнал и обрабатывать их.
def read_data(queue_, should_stop):
while not should_stop.is_set():
# calculate/get cX, cY, angle
queue_.put((cX, cY, angle))
# sleep 30ms
def send_to_ardunio(queue_, should_stop):
while not should_stop.is_set():
data = queue_.get()
cX, cY, angle = data
# update ardunio
def tell_when_to_stop(should_stop):
# detect when to stop, and set the Event. For example, we'll wait for 10 seconds and then ask all to stop
time.sleep(10)
should_stop.set()
queue_ = Queue()
should_stop = Event()
thread_stop_decider = Thread(target=tell_when_to_stop, args=(should_stop,))
thread_read = Thread(target=read_data, args=(queue_, should_stop))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_, should_stop))
thread_read.start()
thread_ardunio.start()
thread_stop_decider.start()
try:
while thread_read.is_alive():
thread_read.join(1)
except KeyboardInterrupt:
should_stop.set()
thread_read.join()
thread_ardunio.join()
thread_stop_decider.join()