多线程作业队列管理器

时间:2009-02-19 12:40:16

标签: c++ multithreading scheduled-tasks threadpool

我需要在交互式应用程序中管理CPU繁重的多任务作业。同样背景,我的具体应用是工程设计界面。当用户调整模型的不同参数和选项时,会在后台运行多个模拟,并在完成时显示结果,即使用户仍在编辑值时也是如此。由于多次模拟需要可变时间(有些是毫秒,有些需要5秒,有些需要10分钟),所以基本上是尽可能快地显示反馈,但通常会中止以前开始但现在不再需要的作业,因为用户的更改已经使它们无效。不同的用户更改可能使不同的计算无效,因此在任何时候我可能有10个不同的模拟运行。某些模拟有多个具有依赖关系的部分(模拟A和B可以单独计算,但我需要它们的结果来模拟C,所以我需要等待A和B在开始C之前先完成。)

我非常有信心处理这种应用程序的代码级方法是某种多线程作业队列。这将包括提交作业以执行,设置任务优先级,等待作业完成,指定依赖项(执行此作业,但仅在作业X和作业Y完成后),取消符合某些条件的作业子集,查询内容的功能工作仍然存在,设置工作线程计数和优先级等等。多平台支持也非常有用。

这些不是软件中的新想法或愿望,但我处于应用程序的早期设计阶段,我需要选择用于管理此类任务的库。我过去在C中编写了自己的粗线程管理器(我认为这是一个通过的仪式),但我想使用现代工具来开展工作,而不是我以前的工作。

首先想到的是OpenMP,但我不确定这是我想要的。 OpenMP非常适合在精细级别进行并行化,自动展开循环等。虽然是多平台,但它也会使用#pragmas侵入您的代码。但主要是它不是为管理大型任务而设计的。尤其是取消挂起的作业或指定依赖项。可能,是的,但它并不优雅。

我注意到Google Chrome uses such a job manager for even the most trivial tasks.设计目标似乎是让用户交互线程尽可能轻松灵活,因此任何可以异步生成的东西都应该是。从查看Chrome源代码来看,这似乎不是一个通用库,但看看设计如何使用异步启动来保持交互速度仍然很有趣。这与我正在做的类似。

还有其他选择:

Surge.Act:用于定义作业的类似Boost的库。它建立在OpenMP之上,但允许链接依赖项,这很好。它似乎不觉得它有一个可以被查询的经理,取消了工作等等。这是一个陈旧的项目所以依赖它是可怕的。

Job Queue与我的想法非常接近,但它是一篇5年前的文章,而不是受支持的图书馆。

Boost.threads确实有很好的平台独立同步,但那不是职业经理人。 POCO具有非常干净的任务启动设计,但同样不是链接任务的完整经理。 (也许我低估了POCO)。

因此,虽然有可用的选项,但我并不满意,我觉得再次推出自己的库的冲动。但我宁愿使用已经存在的东西。即使在搜索之后(这里在SO和网上)我也没有发现任何感觉正确的东西,尽管我认为这必须是一种经常需要的工具,所以肯定有一些社区库或至少是常见的设计。 在SO上有一些posts关于job queues,但似乎没有任何东西适合。

我在这里的帖子是问你所有我错过的现有工具,和/或你如何推出自己的多线程作业队列。

11 个答案:

答案 0 :(得分:17)

我们必须构建自己的作业队列系统以满足与您类似的要求(UI线程必须始终在33ms内响应,作业可以在15-15000ms之间运行),因为那里确实没有任何东西可以满足我们的需求,让独自一人表现得很好。

不幸的是,我们的代码与专有代码一样专有,但我可以为您提供一些最显着的功能:

  • 我们在程序开始时每个核心启动一个线程。每个从全局作业队列中提取工作。作业由一个函数对象和一组关联数据组成(实际上是对func_ptr和void *的详细说明)。线程0(快速客户端循环)不允许在作业上工作,但其余部分尽可能地抓取。
  • 作业队列本身应该是无锁数据结构,例如lock-free singly linked list(Visual Studio comes with one)。避免使用互斥锁;对队列的争用非常高,抓住互斥体的成本很高。
  • 将作业的所有必要数据打包到作业对象本身 - 避免将指针从作业返回到主堆中,在那里您将不得不处理作业和锁之间的争用以及所有这些其他缓慢,恼人的东西。例如,所有模拟参数都应该进入作业的本地数据blob。结果结构显然需要比工作更长的事情:你可以通过以下方式处理:a)即使在完成运行后挂在工作对象上(这样你就可以使用主线程中的内容),或者b)为每个作业专门分配结果结构,并将指针填充到作业的数据对象中。尽管结果本身不会存在于作业中,但这有效地使作业可以独占访问其输出内存,因此您无需使用锁定。

  • 实际上我正在简化上面的内容,因为我们需要准确编排哪些内核上运行的作业,因此每个内核都有自己的作业队列,但这对您来说可能是不必要的。

答案 1 :(得分:5)

我根据Boost.threads推出了自己的。我写这么少的代码后,我感到非常惊讶。如果您没有找到预先制作的东西,请不要害怕自己动手。在Boost.threads和你自己写作之后的经历之间,它可能比你记忆中容易。

对于预制选项,不要忘记Chromium的许可非常友好,因此您可以围绕其代码推出自己的通用库。

答案 2 :(得分:4)

Microsoft正在为下一版Visual Studio 2010开发一组技术,称为并发运行时,并行模式库和异步代理库,它们可能会有所帮助。并发运行时将提供基于策略的调度,即允许您管理和组合多个调度程序实例(类似于线程池但具有实例之间的关联和负载平衡),并行模式库将提供基于任务的编程和与STL类似的并行循环编程模型。代理库提供基于actor的编程模型,并且支持构建并发数据流管道,即管理上述那些依赖性。不幸的是,这还没有发布,所以你可以在team blog上阅读或看一些视频on channel9,还有一个非常大的CTP也可以下载。

如果您正在寻找解决方案,英特尔的Thread Building Blocks和boost的线程库都是很好的库,现在可用。 JustSoftwareSolutions已经发布了一个与C ++ 0x草案匹配的std :: thread实现,当然,如果你正在研究基于细粒度循环的并行性,OpenMP可以广泛使用。

其他人提到的真正挑战是正确识别和分解适合并发执行的任务(即没有不受保护的共享状态),理解它们之间的依赖关系并最大限度地减少瓶颈可能发生的争用(无论是瓶颈是protecting shared state或确保工作队列的调度循环是低争用或无锁定的......并且这样做而不会将实现细节计划泄漏到代码的其余部分。

-Rick

答案 3 :(得分:3)

threadpool之类的东西会对你有用吗?它基于boost :: threads,基本上实现了一个简单的线程任务队列,将队列函数传递给池化线程。

答案 4 :(得分:2)

您可能希望查看 Flow-Based Programming - 它基于异步组件之间的数据块流。有驱动程序的Java和C#版本,以及许多预编码组件。它本质上是多线程的 - 实际上唯一的单线程代码在组件内,尽管您可以为标准调度规则添加时序约束。虽然它可能在你需要的水平上太精细了,但你可以使用这些东西。

答案 5 :(得分:1)

看看boost::future(但另请参阅此discussionproposal),它看起来是并行性的一个非常好的基础(特别是它似乎为C-提供了极好的支持取决于A型和B型的情况。)

我稍微看了一下OpenMP,但是(就像你一样)并不相信它会比Fortran / C数字代码更好用。英特尔Threading Building Blocks看起来对我来说更有趣。

如果涉及到它,那么roll your own在boost :: thread之上并不太难。 [解释:一个线程farm(大多数人称之为池)从线程安全的queue仿函数(任务或工作)中抽取工作。有关使用示例,请参阅testsbenchmark。我有一些额外的复杂功能(可选)支持具有优先级的任务,以及执行任务可以将更多任务生成到工作队列中的情况(这使得知道所有工作实际完成时有点问题;对“待处理”的引用是那些可以处理案件的人)。无论如何,可能会给你一些想法。]

答案 6 :(得分:1)

您可以查看Intel Thread Building Blocks。我相信它可以满足您的需求,而版本2则是开源的。

答案 7 :(得分:1)

那里有很多分布式资源管理器。满足您几乎所有要求的软件是Sun Grid Engine。 SGE用于一些世界上最大的超级计算机,并且正在积极开发中。

TorquePlatform LSFCondor中也有类似的解决方案。

听起来你可能想要自己动手,但上述所有功能都有很多功能。

答案 8 :(得分:0)

我不知道你是否正在寻找一个C ++库(我认为你是这样),但Doug Lea的Java 7的Fork / Join框架非常漂亮,并且完全符合你的要求。您可能能够在C ++中实现它或找到预先实现的库。

更多信息: http://artisans-serverintellect-com.si-eioswww6.com/default.asp?W1

答案 9 :(得分:0)

或许有点迟到,但也看看ThreadWeaver: http://en.wikipedia.org/wiki/ThreadWeaver

答案 10 :(得分:0)

我一直在寻找几乎相同的要求。我正在使用具有4倍分辨率的游戏来开发游戏,并安排完成任务的不同部分几乎使我的大脑大吃一惊。我需要完成一系列复杂的工作,这些工作需要在不同的时间分辨率下完成,并在不同程度的实际模拟中完成,具体取决于玩家主动加载的系统/区域。这意味着随着玩家从一个系统移到另一个系统,我需要将系统加载到当前的高分辨率模拟中,将最后一个系统转移到较低分辨率的模拟中,并对系统的活动/非活动区域执行相同的操作。不同的模拟是基于每个实体的概况的人口,政治,军事和经济活动的大清单。到目前为止,我将尝试描述我的问题和方法,希望它对为您或其他人描述替代方案很有用。我要构建的结构的粗略轮廓将使用以下内容:

  1. cpp-taskflow(现代C ++并行任务编程库)中,我将制作一个模块库,这些模块将用作作业构建部分。每个条目将具有用于初始化和销毁​​的API以及用于通信的指针。我希望使用cpp-taskflow API可以嵌套它们的方式来编写它,以在创建作业时设置所有依赖项,但提供一种实时调整的方法,并提供一个kill-switch。我要做的大部分是状态机的决策树或行为树的状态机,因此作业数据结构将是时间分辨率标记数据的设置和状态,指向实际的统计信息和对象值。
  2. FlatBuffers我正在寻找使用此库来构建“作业列表条目”以及“对象包装”系统。作业队列中的每个条目将是一个平面缓冲区对象,用于描述需要完成的工作(模块的设置),并包含需要完成的工作的数据(或指向数据的共享指针)。对象存储平面缓冲区将包含代表实体表的数据。对我来说,大多数实际数据将由我决定/处理的数组。我还希望将平面缓冲区用作线程之间的通信/控制通道。我很想让一个主“路由器”线程供其他所有人员通信,或者每个线程都包含自己的线程,并具有某种发现机制。
  3. SQLite由于仅活动区域/系统需要完成较高分辨率的工作,因此游戏将创建的某些后台作业列表(针对数千个系统及其实体)将非常庞大且使用寿命长。十万个-数百万个工作(在我看来很重要),每个工作都需要未知的时间来完成。就我而言,我不在乎什么时候完成,只要他们都做(长阵营)。我计划在每个线程中获取一个内存中的sqlite数据库表作为作业队列。每个条目将包含一整块Flatbuffer工作,指向完成时通知的缓冲区的指针,指向用于更新的控制缓冲区的指针以及装饰作业项(位置,数据范围,优先级)的其他字段,这些字段将作为作业条目会产生新的作业,并且随着项目被消耗到数据库中。如果我需要重新处理/更新作业,删除它们及其依赖项,或更新/重新排列优先级或依赖项,这为我提供了一种方法,可以在作业之间创建关系并简单地构造查询。在sqlite db中使用的所有这些都还意味着,我随时可以将整个内容转储到磁盘上,然后再重新加载,或者切换到附加到磁盘并从磁盘进行处理。此外,这使我可以进行很多搜索和排序算法工作,而我通常需要一堆不同类型的容器。能够使用SQL查询为我提供了很多处理作业的选项。

通信队列(作为数据库)是我是否应该通过相应的线程进行访问(每个线程都包含它自己的消息传递db,并且模块API具有用于访问的抽象的锁/互斥锁),这使我感到困惑。或通过某个主路由器线程将所有更新,添加/删除和通信都集中到一个大数据库中。我不知道哪会给我带来最少的静音和锁定麻烦。我花了几天的时间来制作由共享指针指向缓冲池和查找表的巨型意大利面野兽,因此每个线程都拥有自己的缓冲区,并分离出缓冲区。那是我决定只将大型列表转移到sqlite的时候。然后我想,为什么不将其他所有内容的flatbuffer对象输入表中呢?

几乎每个数据库中的所有内容都来自每个模块,我可以编写sql语句来表示需要处理的数据的视图,以及动态地处理数据的方式。将作业本身放在数据库中意味着我也可以为他们做同样的事情。 SQLite具有多线程访问权限,因此将其用作多线程作业队列管理器应该不会花费太多时间。

总而言之,Cpp-Taskflow将允许您使用依赖关系链和作业池多线程来设置复杂的嵌套循环。开箱即用,它具有您需要的大多数结构。 FlatBuffers允许您创建作业声明和对象包装,它们易于作为一个工作单元送入流缓冲区,并在作业线程之间传递它们,而SQLite则允许您以某种方式将流缓冲区作业标记和排队到blob条目中这样一来,只需很少的工作就可以添加,搜索,订购,更新和删除。这也使保存和重新加载变得轻而易举。快照和回滚也应该是可行的,您只需要注意数据库事件的顺序和解决方案即可。

编辑:带着一点盐,但是,我找到了您的问题,因为我正在尝试完成Crashworks描述的内容。我正在考虑使用亲和力来打开长期存在的线程,并让主线程运行大多数Cpp-Taskflow层次结构工作,将工作提供给其他人。我还没有使用工作队列/控制通信的小方法,到目前为止,这只是我的计划。

我希望有人能对此有所帮助。