我遇到了这个练习面试问题。
实现一个作业调度器,该调度器接收一个函数f和一个整数n,并在n毫秒后调用f。
我有一个非常简单的解决方案:
import time
def schedulerX(f,n):
time.sleep(0.001*n)
f
但是,建议的解决方案如下所述。 我不明白所有这些额外代码的目的是什么。 请开导我。
from time import sleep
import threading
class Scheduler:
def __init__(self):
self.fns = [] # tuple of (fn, time)
t = threading.Thread(target=self.poll)
t.start()
def poll(self):
while True:
now = time() * 1000
for fn, due in self.fns:
if now > due:
fn()
self.fns = [(fn, due) for (fn, due) in self.fns if due > now]
sleep(0.01)
def delay(self, f, n):
self.fns.append((f, time() * 1000 + n))
答案 0 :(得分:0)
(理论上)有一些区别。
我认为,第一个也是最重要的一点是,您的解决方案可以一次只调度一个功能。例如,假设您要从现在起10毫秒运行一个函数f1
,然后在10毫秒后运行另一个函数f2
。
您将无法轻松地做到这一点,因为类似schedulerX(f1, 10); schedulerX(f2, 10)
的事情将等待f1
完成运行,然后再开始等待f2
。如果f1
花费一个小时,那么您对f2
的安排将是完全错误的。
第二个版本的目的显然是使计时器和每个函数在单独的线程中运行,以便一个函数调用不会阻塞另一个函数。
但是,正如其他人在评论中指出的那样,导入是错误的,即使问题说明中提到了 a 函数,导入也需要list
个函数,实际上并没有按照我描述的方式工作,或多或少都没有区别。
答案 1 :(得分:0)
正如其他人所指出的那样,您的解决方案是“阻止”的:它阻止了等待运行时发生的任何其他情况。建议的解决方案的目的是让您安排工作,然后同时进行其他工作。
关于建议代码的作用的解释:
您首先要创建一个Scheduler
,这将启动它自己的线程,该线程有效地在后台运行,并运行作业。
scheduler = Scheduler()
然后,您可以在代码中安排所需的任何作业,而不必等待它们运行:
def my_recurring_job():
# Do some stuff in the background, then re-run this job again
# in one second.
### Do some stuff ###
scheduler.delay(my_recurring_job, 1000)
scheduler.delay(lambda: print("5 seconds passed!"), 5 * 1000)
scheduler.delay(lambda: print("2 hours passed!"), 2 * 60 * 60 * 1000)
scheduler.delay(my_recurring_job, 1000)
# You can keep doing other stuff without waiting
调度程序的线程只是在其poll
方法中永久循环,运行时间已到的所有作业,然后休眠0.01秒并再次检查。代码中有一个小错误,如果现在==到期,该作业将不会运行,但也不会保留。应该改为if now >= due:
。
更高级的调度程序可以使用threading.Condition
而不是每秒轮询100次:
import threading
from time import time
class Scheduler:
def __init__(self):
self.fns = [] # tuple of (fn, time)
# The lock prevents 2 threads from messing with fns at the same time;
# also lets us use Condition
self.lock = threading.RLock()
# The condition lets one thread wait, optionally with a timeout,
# and lets other threads wake it up
self.condition = threading.Condition(self.lock)
t = threading.Thread(target=self.poll)
t.start()
def poll(self):
while True:
now = time() * 1000
with self.lock:
# Prevent the other thread from adding to fns while we're sorting
# out the jobs to run now, and the jobs to keep for later
to_run = [fn for fn, due in self.fns if due <= now]
self.fns = [(fn, due) for (fn, due) in self.fns if due > now]
# Run all the ready jobs outside the lock, so we don't keep it
# locked longer than we have to
for fn in to_run:
fn()
with self.lock:
if not self.fns:
# If there are no more jobs, wait forever until a new job is
# added in delay(), and notify_all() wakes us up again
self.condition.wait()
else:
# Wait only until the soonest next job's due time.
ms_remaining = min(due for fn, due in self.fns) - time()*1000
if ms_remaining > 0:
self.condition.wait(ms_remaining / 1000)
def delay(self, f, n):
with self.lock:
self.fns.append((f, time() * 1000 + n))
# If the scheduler thread is currently waiting on the condition,
# notify_all() will wake it up, so that it can consider the new job's
# due time.
self.condition.notify_all()