将大型单片单线程应用程序转换为多线程架构的建议?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将大型单片单线程应用程序转换为多线程架构的建议?相关的知识,希望对你有一定的参考价值。

我公司的主要产品是大型单片C ++应用程序,用于科学数据处理和可视化。它的代码库可以追溯到12年或13年,虽然我们已经将工作投入到升级和维护中(使用STL和Boost - 当我加入大多数容器时都是自定义的,例如 - 完全升级到Unicode和2010 VCL等)还有一个非常重要的问题:它是完全单线程的。鉴于它是一个数据处理和可视化程序,这越来越成为一个障碍。

我既是开发人员又是下一个版本的项目经理,我们希望解决这个问题,这对于这两个领域来说都是一项艰巨的任务。我正在寻求有关如何解决问题的具体,实用和建筑建议。

程序的数据流可能是这样的:

  • 窗口需要绘制数据
  • 在paint方法中,它将调用GetData方法,通常在一次绘制操作中为数百位数据调用数百次
  • 这将从文件或其他任何需要的计算或读取(通常非常复杂的数据流 - 将此视为流经复杂图形的数据,每个节点执行操作)

即,绘制消息处理程序将在处理完成时阻止,如果数据尚未计算和缓存,则可能需要很长时间。有时这是几分钟。执行冗长处理操作的程序的其他部分也会出现类似的路径 - 程序在整个时间(有时是几小时)内没有响应。

我正在寻求如何改变这一点的建议。实用的想法。也许这样的事情:

  • 设计用于异步请求数据的模式?
  • 存储大量对象,以便线程可以安全地读写?
  • 在某些东西试图读取时处理数据集的失效?
  • 有这种问题的模式和技术吗?
  • 我应该问什么,我没有想到?

自从几年前我的Uni时代以来,我没有做任何多线程编程,我认为我团队的其他成员处于类似的位置。我所知道的是学术上的,而不是实际的,并且远远不足以让人有信心接近这一点。

最终目标是拥有一个完全响应的程序,其中所有计算和数据生成都在其他线程中完成,并且UI始终响应。我们可能无法在一个开发周期中到达那里:)


编辑:我想我应该添加一些关于该应用程序的更多细节:

  • 它是适用于Windows的32位桌面应用程序。每个副本都是许可的。我们计划将其作为桌面本地运行的应用程序
  • 我们使用Embarcadero (formerly Borland) C++ Builder 2010进行开发。这会影响我们可以使用的并行库,因为大多数似乎(?)只为GCC或MSVC编写。幸运的是,他们正在积极开发它,它的C ++标准支持比以前好多了。编译器支持these Boost components
  • 它的架构并不像应该的那样干净,而且组件通常耦合得太紧。这是另一个问题:)

编辑#2:感谢您的回复!

  • 令我感到惊讶的是,很多人推荐了一个多进程架构(这是目前最受欢迎的答案),而不是多线程。我的印象是,这是一个非常Unix的程序结构,我对它的设计或工作方式一无所知。 Windows上有关于它的可用资源吗?它在Windows上真的很常见吗?
  • 就一些多线程建议的具体方法而言,是否存在异步请求和消费数据,线程软件或异步MVP系统的设计模式,或者如何设计面向任务的系统,或者文章和书籍以及发布后的解构说明工作的东西和不起作用的东西?当然,我们可以自己开发所有这些架构,但是与其他人之前的工作一起工作并知道要避免哪些错误和陷阱是很好的。
  • 在任何答案中没有涉及的一个方面是项目管理。我的印象是估计这需要多长时间,并在做一些不确定的事情时保持对项目的良好控制,因为这可能很难。这就是为什么我会在配方或实际编码建议之后,尽可能地引导和限制编码方向。

我还没有为这个问题找到答案 - 这不是因为答案的质量,这很好(而且比你还要好),但仅仅是因为我的范围,我希望得到更多的答案或讨论。谢谢那些已经回复的人!

答案

因此,您对算法的描述提示如何继续:

通常是非常复杂的数据流 - 将此视为流经复杂图形的数据,每个节点都执行操作

我会考虑使数据流图表实际上是完成工作的结构。图中的链接可以是线程安全的队列,每个节点的算法可以保持不变,除非包含在从队列中获取工作项并将结果存储在一个队列中的线程中。你可以更进一步,使用套接字和进程而不是队列和线程;如果这样做有性能优势,这将让您分布在多台机器上。

然后你的绘画和其他GUI方法需要分成两部分:一半用于排队工作,另一半用于绘制或使用结果,因为它们从管道中出来。

如果应用假设数据是全局的,这可能不实用。但是如果它很好地包含在类中,正如您的描述所暗示的那样,那么这可能是使其并行化的最简单方法。

另一答案

你也可以看看Herb Sutter You have a mass of existing code and want to add concurrency. Where do you start?的这篇文章

另一答案

嗯,我认为你期待很多基于你的评论。你不会通过多线程从几分钟到几毫秒。您最希望的是当前时间量除以核心数量。话虽这么说,你对C ++有点运气。我编写了高性能多处理器科学应用程序,你想要寻找的是你能找到的最多的embarrassingly parallel循环。在我的科学代码中,最重的部分是计算100到1000个数据点。但是,所有数据点都可以独立于其他数据点进行计算。然后,您可以使用openmp拆分循环。这是最简单,最有效的方法。如果您的编译器不支持openmp,那么移植现有代码将非常困难。使用openmp(如果你很幸运),你可能只需添加几个#pragmas即可获得4-8倍的性能。这是StochFit的一个例子

另一答案

我希望这可以帮助您轻松理解和转换单片线程应用程序到多线程。对不起,这是另一种编程语言,但从来没有解释过的原则是相同的。

http://www.freevbcode.com/ShowCode.Asp?ID=1287

希望这可以帮助。

另一答案

您必须做的第一件事是将GUI与数据分开,第二件事是创建一个多线程类。

第1步 - 响应式GUI

我们可以假设您生成的图像包含在TImage的画布中。您可以在表单中放置一个简单的TTimer,您可以编写如下代码:

if (CurrenData.LastUpdate>CurrentUpdate)
    {
    Image1->Canvas->Draw(0,0,CurrenData.Bitmap);
    CurrentUpdate=Now();
    }

好!我知道!有点脏,但它很快而且很简单。重点是:

  1. 您需要在主线程中创建的Object
  2. 只有在需要且安全的情况下才能将对象复制到您需要的表单中(好吧,可能需要更好的保护Bitmap,但为了简洁......)
  3. 对象CurrentData是生成图像的实际项目,单线程

现在您拥有一个快速响应的GUI。如果您的算法速度很慢,刷新速度很慢,但您的用户永远不会认为您的程序已冻结。

第2步 - 多线程

我建议你实现如下的类:

SimpleThread.h

typedef void (__closure *TThreadFunction)(void* Data);

class TSimpleThread : public TThread
{
public:
    TSimpleThread( TThreadFunction _Action,void* _Data = NULL, bool RunNow = true );
    void AbortThread();

    __property Terminated; 

protected:
    TThreadFunction ThreadFunction;
    void*           Data;

private:
    virtual void __fastcall Execute() { ThreadFunction(Data); };
};

SimpleThread.c

TSimpleThread::TSimpleThread( TThreadFunction _Action,void* _Data, bool RunNow)
             : TThread(true), // initialize suspended
               ThreadFunction(_Action), Data(_Data)
{
FreeOnTerminate = false;
if (RunNow) Resume();
}

void TSimpleThread::AbortThread()
{
Suspend(); // Can't kill a running thread
Free();    // Kills thread
}

我们来解释一下。现在,在您的简单线程类中,您可以创建一个这样的对象:

TSimpleThread *ST;
ST=new TSimpleThread( RefreshFunction,NULL,true);
ST->Resume();

让我们更好地解释一下:现在,在你自己的单片类中,你创建了一个线程。更多:您在一个单独的线程中引入一个函数(即:RefreshFunction)。您的函数的范围是相同的,类是相同的,执行是分开的。

另一答案

我的第一个建议,虽然它已经很晚了(对于恢复旧线程而言,这很有意思!)正在寻找同构变换循环,其中循环的每次迭代都会改变来自其他迭代的完全独立的数据。

而不是考虑如何将这个旧的代码库变成一个并行运行各种操作的异步代码库(这可能会导致各种麻烦比从不良锁定模式或指数恶化的单线程性能更糟糕,竞争条件/死锁通过尝试在事后看到代码,你无法完全理解),坚持现在的整体应用程序设计的顺序思维模式,但识别或提取简单,均匀的变换循环。不要从侵入性广泛的设计级多线程出发,然后尝试深入细节。从优先实现细节和特定热点的非侵入式多线程开始工作。

我所说的齐次循环基本上是一种以非常简单的方式转换数据的方法,例如:

for each pixel in image:
    make it brighter

这是非常简单的理由,你可以安全地并行化这个循环,没有任何问题使用OMP或TBB或其他什么,而不会纠缠于线程同步。只需浏览一下这段代码即可完全理解其副作用。

尝试找到适合这种类型的简单齐次变换循环的尽可能多的热点,如果你有复杂的循环,用复杂的控制流更新许多不同类型的数据,触发复杂的副作用,那么寻求重构这些同类循环。通常,对3种不同类型的数据产生3个不同副作用的复杂循环可以变成3个简单的同质循环,每个循环对一种类型的数据触发一种副作用,具有更简单的控制流。做多个循环而不是一个循环可能看起来有点浪费,但循环变得更简单,同质性通常会导致更多缓存友好的顺序内存访问模式与零星的随机访问模式,然后您往往会找到更多的机会以直接的方式安全地并行化(以及矢量化)代码。

首先,您必须彻底了解您尝试并行化的任何代码的副作用(我的意思是彻底!!!),因此寻找这些同类循环可以为您提供代码库的孤立区域,您可以根据副作用轻松推理到了可以自信而安全地并行化这些热点的程度。它还可以很容易地推断出特定代码片段中正在发生的状态变化,从而提高代码的可维护性。保存超级多线程应用程序的梦想,并行运行所有内容以供日后使用。目前,专注于识别/提取性能关键,均匀的循环,具有简单的控制流程和简单的副作用。这些是使用简单并行化循环进行并行化的优先目标。

现在不可否认,我有点回避你的问题,但是如果你按照我的建议行事,大多数都不需要申请,至少在你有点努力工作到你正在考虑更多关于多线程设计的地步之前简单地并行化实现细节。而且你可能甚至不需要走得太远而在性能方面拥有非常有竞争力的产品。如果你在一个循环中做了大量的工作,你可以投入硬件资源来使循环更快,而不是同时运行许多操作。如果你不得不求助于更多的异步方法,比如你的热点是否有更多的I / O限制,请寻求异步/等待方法,在这种方法中你可以触发异步任务,但在此期间做一些事情,然后等待异步任务去完成。即使这不是绝对必要的,但我们的想法是尽可能地分离代码库中的孤立区域,100%置信度(或至少99.9999999%)表示多线程代码是正确的。

你不想在竞争条件下赌博。没有什么比找到一些模糊的竞争条件更令人沮丧的了,只有在一个随机用户的机器上满月时,你的整个QA团队都无法重现它,只有在3个月之后,自己遇到它,除非在那一次你在没有调试信息的情况下运行了一个发布版本,然后你就可以在睡眠中投入并知道你的代码库在任何特定的时刻都会崩溃,但是没有人能够始终如一地重现。因此,使用多线程遗留代码库可以轻松实现,至少目前如此,并坚持使用多线程隔离但代码库的关键部分,其中副作用很容易理解。并测试它的垃圾 - 理想情况下应用TDD方法,你编写一个测试你要多线程的代码,以确保它在你完成后给出正确的输出...虽然竞争条件是那种类型的东西很容易在单元和集成测试的雷达下飞行,所以在你尝试多线程之前,你绝对需要能够理解给定代码段中发生的所有副作用。最好的方法是使用最简单的控制流程使副作用尽可能容易理解,从而导致整个循环只产生一种副作用。

另一答案

很难给你适当的指导。但...

根据我的最简单方法是将您的应用程序转换为ActiveX EXE,因为COM支持线程等,内置于其中,您的程序将自动成为多线程应用程序。当然,您必须对代码进行相当多的更改。但这是最短,最安全的方式。

我不确定但是RichClient Toolset lib可能会为你做这个伎俩。在作者网站上写道:

它还提供ActiveX-Dlls的免注册加载/实例化功能以及易于使用的新线程方法,该方法与命名管道一起工作,因此也可以进行交叉处理。

Please check it out.谁知道它可能是满足您要求的正确解决方案。

对于项目管理,我认为您可以通过插件将其与SVN集成,继续使用您选择的IDE中提供的内容。

我忘了提到我们已经完成了一个股票市场申请,该申请根据我们开发的算法自动交易(基于低点和高点的买入和卖出)到用户组合中的脚本。

在开发此软件时,我们遇到的问题与您在此处说明的相同。为了解决这个问题,我们在ActiveX EXE中转换了应用程序,并将所有需要并行执行的部分转换为ActiveX DLL。我们还没有使用任何第三方库!

HTH

另一答案

你面前有一个很大的挑战。我面临着类似的挑战 - 15年的单片单线程代码库,没有利用多核等等。我们花了很多精力去寻找一个可行且可行的设计和解决方案。

首先是坏消息。它将介于不切实际和不可能使您的单线程应用程序多线程之间。单线程应用程序依赖于它的单线程,这种方式既微妙又粗略。一个例子是计算部分是否需要来自GUI部分的输入。 GUI必须在主线程中运行。如果您尝试直接从计算引擎获取此数据,您可能会遇到需要重新设计修复的死锁和竞争条件。在设计阶段,甚至在开发阶段,这些依赖性中的许多都不会出现,但只有在将版本构建置于恶劣环境中之​​后才会出现。

更多坏消息。编程多线程应用程序非常困难。只是锁定东西并做你必须做的事情似乎相当简单,但事实并非如此。首先,如果您锁定所有内容,最终会序列化您的应用程序,首先否定多线程的所有好处,同时仍然增加所有复杂性。即使你超越了这一点,编写一个无缺陷的MP应用程序也很难,但编写一个高性能的MP应用程序要困难得多。你可以通过火灾在一种洗礼中学习这项工作。但是,如果您使用生产代码(尤其是遗留生产代码)执行此操作,则会使您的业务面临风险。

现在好消息。您确实拥有不涉及重构整个应用程序的选项,并且会为您提供所需的大部分内容。一个选项特别容易实现(相对而言),并且比使您的应用程序完全MP更不容易出现缺陷。

您可以实例化应用程序的多个副本。使其中一个可见,而其他所有其他都不可见。使用可见应用程序作为表示层,但不要在那里进行计算工作。相反,将消息(可能通过套接字)发送到应用程序的不可见副本,这些副本执行工作并将结果发送回表示层。

这可能看起来像是一个黑客。也许是。但是,如果不将系统的稳定性和性能置于极大的风险之中,它将为您提供所需的功能。此外还有隐藏的好处。一个是应用程序的隐形引擎副本可以访问自己的虚拟内存空间,从而可以更轻松地利用系统的所有资源。它也可以很好地扩展。如果您在2核盒子上运行,则可以分离2个引擎副本。 32芯? 32份。你明白了。

另一答案
  1. 不要尝试多线程旧应用程序中的所有内容。为了说它是多线程的多线程是浪费时间和金钱。你正在构建一个可以做某事的应用程序,而不是你自己的纪念碑。
  2. 剖析并研究您的执行流程,以确定应用程序花费大部分时间的位置。分析器是一个很好的工具,但是只需单步执行调试器中的代码即可。你可以在随机游走中找到最有趣的东西。
  3. 将UI与长时间运行的计算分离。使用跨线程通信技术从计算线程向UI发送更新。
  4. 作为#3的副作用:仔细考虑重入:现在计算在后台运行,用户可以在UI中浏览,UI中的哪些内容应该被禁用以防止与后台操作冲突?允许用户在对该数据运行计算时删除数据集可能是个坏主意。 (缓解:计算会生成数据的本地快照)用户是否同时处理多个计算操作?如果处理得当,这可能是一项新功能,有助于合理化应用程序返工工作。如果被忽视,那将是一场灾难。
  5. 确定要被推入后台线程的候选特定操作。理想的候选者通常是一个单一的功能或类,它可以完成很多工作(需要“很多时间”才能完成 - 超过几秒钟),具有明确定义的输入和输出,不使用全局资源,并且不要直接触摸UI。根据改造到这个理想所需的工作量来评估和优先考虑候选人。
  6. 在项目管理方面,一步一步。如果您有多个操作是可以移动到后台线程的强大候选者,并且它们彼此之间没有交互,那么这些操作可能由多个开发人员并行实现。但是,让每个人都先参与一次转换是一个很好的练习,这样每个人都能理解要查找的内容,并建立用户界面交互的模式等。举行扩展的白板会议,讨论提取一个转换的设计和过程。函数进入后台线程。去实施(一起或分发给个人),然后重新召集,将它们放在一起,讨论发现和痛点。
  7. 多线程是一个令人头疼的问题,需要比直接编码更仔细的思考,但将应用程序分成多个进程会产生更多麻烦,IMO。线程支持和可用的原语在Windows中很好,可能比其他一些平台更好。使用它们。
  8. 一般来说,不要做任何需要的事情。通过抛出更多模式和标准库,很容易严重过度实现并使问题复杂化。
  9. 如果您的团队中没有人以前做过多线程工作,那么预算时间可以让专家或资金聘请一名顾问。
另一答案

您要做的主要是将UI与数据集断开连接。我建议这样做的方法是在两者之间加一层。

您需要设计一个用于显示的数据数据结构。这很可能包含一些后端数据的副本,但“煮熟”以便于绘制。这里的关键想法是,这是快速和容易绘画。您甚至可能让此数据结构包含数据位的计算屏幕位置,以便快速绘制。

每当您收到WM_PAINT消息时,您应该获得此结构的最新完整版本并从中进行绘制。如果您正确执行此操作,您应该能够每秒处理多个WM_PAINT消息,因

以上是关于将大型单片单线程应用程序转换为多线程架构的建议?的主要内容,如果未能解决你的问题,请参考以下文章

将带有 Gradle 的单片 Spring Boot 应用程序转换为多模块失败

从单线程到多线程图像处理

unity打包webgl局限及优化建议

unity打包webgl局限及优化建议

unity打包webgl局限及优化建议

unity打包webgl局限及优化建议