设想有一个轮询任务
我们需要从一个第三方系统监视一个状态,等待它变成我们期待的预期值
由于状态存在永远不会达到预期的可能,我们需要为这个任务设置一个忍耐的上限,即超时时间
这个任务写成接口的定义
···
def watch_job(expect_state, interval, timeout):
···
interval 是向第三方系统轮询的间隔 假设这里有一个API
表达这个任务的方式有几种
假设这个 API 是 request_state() , 它返回一个状态
伪代码写成
def watch_job(expect_state, interval, timeout):
acc_time = 0 # 表示累计轮询的时间
while acc_time < timeout:
s = request_state()
if s in expect_state:
return True, s
time.sleep(interval)
acc_time += interval
else:
return False, s
另一种写法
def watch_job(expect_state, interval, timeout):
times = timeout // interval # 表示预期轮询的轮数
cnt = 0 # 表示累计轮询的次数
while cnt < times:
s = request_state()
if s in expect_state:
return True, s
time.sleep(interval)
cnt += 1
else:
return False, s
以下对比两种写法的微妙差异:
- 第一种方法当 interval 传入0 值之后,有可能陷入一个死循环,而第二种会引起一个除零错误——这会被立刻发现
- 对于interval 负值的情况,第一种仍然是一个死循环,而第二种马上会结束
第二种写法,只会出现异常,而不会引起死循环。
当然,第一种写法节省了一个变量
在安全性方面,似乎第二种写法更具有稳定性
现在考虑另一个问题 —— timeout是60s 的话,实际轮询时间会是多少?
这取决于 request_state 接口的好是,两种写法都没有考虑把 request_state 的耗时纳入考量,这显得 timeout 实际上是个假时间,如果确切一点的话,这个命名可以改成次数上限一类的名字
写成下面的的样子
def watch_job(expect_state, interval, times):
cnt = 0 # 表示累计轮询的次数
while cnt < times:
s = request_state()
if s in expect_state:
return True, s
time.sleep(interval)
cnt += 1
else:
return False, s
request_state 会可能耗时很长,可能触发异常
那么实际轮询的上限时间会不太确定,只是大概能估计它的范围而已。
考虑稳定性,我们可能还需要对 request_state 包上try-except 语句块,让它在爆出异常的时候不会立刻结束轮询
总之,对于一个长时间的控制块,设计程序时应当尽量避免死循环,内存泄露等等让人难以debug的错误引入。
网友评论