我想用 C++ 创建一个非常高效的任务调度系统。
基本思想是这样的:
class Task {
public:
virtual void run() = 0;
};
class Scheduler {
public:
void add(Task &task, double delayToRun);
};
在 Scheduler
后面应该有一个固定大小的线程池,用来运行任务(我不想为每个任务创建一个线程)。 delayToRun
means that the task
doesn’t get executed immediately, but delayToRun
seconds later (measuring from the point it was added into the Scheduler
)。
( delayToRun
当然是一个“至少”的值。如果系统被加载,或者如果我们向调度器询问不可能,它将无法处理我们的请求。但它应该尽力而为)
这是我的问题。如何有效地实现 delayToRun
功能?我试图通过使用互斥锁和条件变量来解决这个问题。
我看到两种方法:
带经理线程
调度程序包含两个队列: allTasksQueue
和 tasksReadyToRunQueue
。一个任务被添加到 allTasksQueue
在 Scheduler::add
。有一个管理器线程,它等待的时间最短,因此它可以将任务从 allTasksQueue
到 tasksReadyToRunQueue
。工作线程等待 tasksReadyToRunQueue
中可用的任务。
如果 Scheduler::add
在 allTasksQueue
前面添加一个任务(一个任务,它的值为 delayToRun
所以它应该在当前任务之前最快运行),则需要唤醒管理器任务,以便更新等待时间。
这种方法可以被认为是低效的,因为它需要两个队列,并且它需要两个 condvar.signals 来使任务运行(一个用于 allTasksQueue
-> tasksReadyToRunQueue
,一个用于发送信号工作线程来实际运行任务)
没有经理线程
调度程序中有一个队列。一个任务在 Scheduler::add
被添加到这个队列中。工作线程检查队列。如果它是空的,它会在没有时间限制的情况下等待。如果不为空,则等待最快的任务。
如果工作线程等待的条件变量只有一个:这种方法可以认为是低效的,因为如果队列前面增加了一个任务(前面的意思是,如果有N个工作线程,那么任务索引)然后需要唤醒 所有 工作线程以更新他们正在等待的时间。
如果每个线程有单独的条件变量,那么我们可以控制唤醒哪个线程,所以这种情况下我们不需要唤醒所有线程(只需要唤醒等待时间最长的线程) ,所以我们需要管理这个值)。我目前正在考虑实现这一点,但确定确切的细节很复杂。对这种方法有什么建议/想法/文件吗?
这个问题有更好的解决方案吗?我正在尝试使用标准 C++ 功能,但如果它们提供更好的解决方案,我也愿意使用平台相关(我的主要平台是 linux)工具(如 pthreads),甚至是 linux 特定工具(如 futexes)。
原文由 geza 发布,翻译遵循 CC BY-SA 4.0 许可协议
通过使用单个池线程等待“下一个运行”任务的设计,您可以避免拥有单独的“管理器”线程,并且在下一个运行的任务更改时必须唤醒大量任务(如果有)一个条件变量,其余的池线程无限期地等待第二个条件变量。
池线程将按照以下几行执行伪代码:
如果您更改要运行的下一个任务,则执行:
持有
queue_lock
。在这种方案下,所有唤醒都直接在一个线程上,只有一个优先级任务队列,不需要管理线程。