与长时间运行的Python进程交互
我在树莓派上运行了一个长时间的Python程序,这个程序是无头模式的(也就是说没有图形界面),主要是用来控制我的花园,代码大概是这样的:
from time import sleep
def run_garden():
while 1:
/* do work */
sleep(60)
if __name__ == "__main__":
run_garden()
这个程序每60秒会休眠一次,这段时间足够我花园里的各种变化(比如湿度、空气温度、开启水泵、关闭风扇等)被处理。但是,如果我想手动干预这些设置,该怎么办呢?
目前,在我的/* do work */
循环中,我首先会去调用另一个服务器,那里的配置变量可以更新。我可以通过一个网页控制台来更新这些配置变量,但这就显得有些不够实时,因为它还是依赖于60秒的循环(比如你在网页控制台上更新了设置,然后可能要等45秒才能看到效果)。
运行run_garden()
的树莓派是专门用来管理花园的,基本上没有其他程序在占用资源。所以我知道我可以做点什么,只是不知道该怎么做。
一旦循环检测到配置变量被更新,它可以采用指数退避的方式来不断检查是否有新的操作,而不是等60秒,但这样似乎也没有太大的改善。
有没有更好的方法可以直接干预这个长时间运行的程序呢?
3 个回答
Python的select
模块听起来可能会很有用。
如果你曾经使用过Unix系统的类似功能(比如在套接字编程中),那你会觉得很熟悉。
如果没有的话,这里有一个我常推荐的C语言套接字参考中的select
部分。还有这里是关于这个模块的一个不错的介绍.
需要注意的是,第一个参考是关于C语言的,而不是Python,但select
系统调用的概念是一样的,所以讨论内容可能会对你有帮助。
简单来说,这个模块可以让你告诉它你对哪些事件感兴趣(比如,套接字数据到达、键盘事件),然后它会一直等待,直到你指定的超时时间到或者事件发生。
如果你在使用套接字,那么把套接字和标准输入(stdin)加入到你感兴趣的事件列表中是很简单的。如果你只是想找个方法“条件性地睡眠”60秒,除非检测到按键,这个方法同样适用。
补充说明:
另一种解决方案是让你的树莓派“注册”到运行网页控制台的服务器上。这可能需要额外的工作,但能让你实现你想要的实时效果。
基本上,树莓派通过通知服务器自己的存在来“注册”,服务器会存储这个设备的地址。如果使用TCP,你可以保持连接打开(这在处理防火墙时可能很重要)。如果使用UDP,你可以在注册之前绑定设备的端口,这样服务器就能响应“公告”的源地址。
一旦注册,当服务器上的配置选项发生变化时,通常会有两种情况发生:
A) 你会向设备发送一个小的“ping”(这里是指一般意义上的,不是ICMP主机检测协议),通知它配置选项已经改变。此时,设备会立即请求完整的配置集,以获取更新。
B) 你将更新后的配置选项(或者整个配置集)发送回设备。这种方式减少了设备和服务器之间的消息数量,但可能需要更多的工作,因为这似乎与当前的设置有些偏离。
为什么不使用基于事件的循环,而是让程序在某段时间内“睡觉”?
这样的话,你的循环就只会在检测到变化时运行,而且每次有变化时都会运行(这正是你问题的关键?)。
你可以通过使用:
Python 的事件对象来实现这样的功能。
只需等待一个或多个事件对象被触发,然后再运行循环。你也可以根据需要,等待多个事件完成等等,这取决于你是否期望某个变量会频繁更新。
或者使用一种系统,比如:
广播事件。
在你的主循环中监听一个套接字。给你的套接字读取操作设置一个超时时间(比如60秒,也就是下次花园更新的时间),这样在没有收到命令的时候,你至少每分钟就能回到正常的功能。
如果你希望花园维护的更新每分钟不能更快地进行一次,那么你需要检查一下自上次更新以来已经过去了多久,因为当有命令进来的时候,读取操作会完成得快得多。