并行和事件驱动基本上是正交的,尽管并行化事件通常“容易”。我将首先介绍事件驱动,然后介绍并行化,尽管您可能只想使用后者。
python 中的“正常”控制流是迭代的。
这意味着您定义了代码应该执行的指令,然后 pc 一步一步地执行这些步骤。
有不同的方式来组织你的代码(函数式、事件驱动、面向对象,虽然我不想说这些是你只能做 X 或 Y 的绝对类别)。事件驱动通常意味着您定义事件以及如何处理它们。
没有什么可以用事件驱动来编程,而你不能迭代编程,反之亦然。
在引入asyncio 库时,Python 主要在 3.4 版中获得了对 asyncronuos 的支持。
在 3.5 中,您还获得了语法糖 await 和 async。因为您使用的是 2.7,所以这对您不可用。
有一个来自 asyncio 的名为 trollius 的反向移植,但如果您只有“少量事件”,这将是多余的。此外,“滚动您自己的基本事件循环”并不难(当然 asyncio 和 trollius 做得更多,但如果我们不打算使用这些功能,何必费心呢?)。
基本工作流程是等待事件,然后在它们发生时处理它们:
events = []
while waiting_for_events:
if events:
event = events.pop()
handle_event(event)
您需要知道如何区分事件以及如何处理它们。
对于“功能齐全的事件循环”,您可能会使用不同的继承类,但我们只需为每个事件使用一个名称。
此外,我们可能还需要某种data,比如我们遇到的 RFID。
from collections import namedtuple
Event = namedtuple("Event", ["name", "data"])
然后我们只需要将事件映射到如何处理它们:
def handle_rfid(data):
...
def handle_timer(data):
...
event_handler_mapping = {"rfid": handle_rfid, "timer": handle_timer}
def handle_event(event):
event_handler_mapping[event.name](event.data)
我们仍然需要生成事件,所以让我们重写事件循环来获取事件:
timer = 0
current_time = time.perf_counter()
while waiting_for_events:
rfid = get_rfids()
if rfid:
events.append(Event("rfid", rfid))
if timer > 1:
events.append(Event("timer", timer))
timer = 0
else:
timer += time.perf_counter() - current_time
current_time = time.perf_counter()
if events:
event = events.pop()
handle_event(event)
现在我们是“事件驱动的”。好消息是我们可以轻松地将其扩展到更多事件。
坏事是它仍然做你可能已经拥有的同样的事情,但它更复杂。
此外,如果事件处理需要大量时间(更新电子表格似乎就是这种情况),其他事件将不会
生成和处理。这是并行性发挥作用。
并行基本上意味着我们可以使用多个内核。
在这里,我们实际上只需要“并发”,这意味着可以同时发生两件事。
这比真正的并行“更容易”,我们可以在不同的事情之间切换,但仍然按顺序执行所有事情。
在 python 中,这基本上归结为多处理(并行)和线程(“仅”并发)(在其他编程语言中,线程实际上是并行的,但在 python 中,这是出于我不想进入这种情况的原因)。
并发的问题总是同步。如果事情可以同时发生,坏事就会发生
如果两个线程试图改变同一个变量。一般来说,只要你只使用线程安全的函数来访问线程之间共享的变量,你就是安全的。
在 python 中,线程是由threading 模块创建的。
如果您还不知道其他地方的线程,我个人觉得很难理解,但要点如下:
要在线程中运行函数,请使用threading.Thread(target=function),然后使用thread.start()。
你可以使用它:
def run_in_thread(f, *args, **kwargs):
thread = Thread(target=f, args=args, kwargs=kwargs)
thread.start()
def _update_spreadsheet(data):
# logic here
# when using the event driven approach from above
def handle_timer(data):
run_in_thread(_update_spreadsheet(data))
请注意,如果您从_update_spreadsheet 中访问变量,则需要注意仅使用线程安全函数。
尽可能少地使用线程间通信是“最好的”。
queue 通常是一个不错的选择。
您可以在没有事件驱动组织的情况下使用并行性/并发性。
因为我们已经将代码划分为事件处理程序,所以我们可以在单独的线程中调用长时间运行的事件处理程序。
如果我们有很多事件和事件处理程序在线程中运行所有内容是一个坏主意(因为线程切换有开销)。
因此 asyncio(可能还有所有其他事件循环)实现了某种“等到至少一个事件可以被处理”。
这对于互联网输入和输出来说是最有趣的,因为这些需要“很长时间”。
通常使用select 之类的东西。
其他事件(计时器、从磁盘读取、等待某些硬件事件……)需要其他机制来“在发生某些事情时唤醒我”。集成所有这些是 asyncio 为您提供的功能之一。