【问题标题】:Threading vs Task-Based vs Asynchronous Programming线程 vs 基于任务 vs 异步编程
【发布时间】:2013-12-06 21:51:43
【问题描述】:

我是这个概念的新手。这些是相同的还是不同的东西?有什么不同?我真的很喜欢能够同时运行两个进程的想法,例如,如果我有几个大文件要加载到我的程序中,我希望同时加载尽可能多的文件,而不是一次等待一个。并且在处理大文件时,例如 wav 文件,最好将其分解为多个块并一次处理多个块,然后将它们重新组合在一起。我想研究什么来学习如何做这种事情?

编辑:另外,我知道在多核处理器上使用多个内核适合这里的某个地方,但显然异步编程并不一定意味着您使用多个内核?如果没有多个内核可以利用,为什么要这样做?

【问题讨论】:

  • 异步并不一定意味着多线程。同时加载多个文件可能不会为您节省任何时间,实际上可能会更慢,因为磁盘驱动器一次只能做一件事。您需要将文件的输入缓冲区增加到 64K 左右。一般来说,你了解这些东西的方式是获取一个教程并开始使用它。不过,我对 C++ 没有任何建议。
  • 很难完全回答你的问题,因为有很多话要说,但是理解异步编程的一个好方法是编写一个使用多个线程和函数回调的程序。
  • 如果我有多个内核,似乎在不同内核上以单独的块处理一个大文件肯定会提高性能。我可以用 std::thread 做到这一点吗?如果线程不是异步运行某些东西的唯一方法,那么还有哪些其他方法?
  • Scott Meyers 在他最新的 Effective Modern C++ 中有一节介绍 C++11 并发以及线程和任务使用之间的区别Item 35: Prefer task based programming to thread based

标签: c++ multithreading asynchronous


【解决方案1】:

它们相关但不同。

线程,通常称为多线程,是指在单个进程中使用多个执行线程。这通常是指使用一小组线程的简单情况,每个线程执行需要或可能受益于同时运行的不同任务。例如,一个 GUI 应用程序可能有一个线程绘制元素,另一个线程响应鼠标点击等事件,另一个线程执行一些后台处理。

但是,当线程数量达到极限时,每个线程都在做自己的事情,我们通常会开始谈论Agent-based approach

基于任务的方法指的是软件工程中的一种特定策略,在这种策略中,抽象地说,您可以动态创建要完成的“任务”,这些任务由任务管理器负责将任务分配给可以完成它们的线程。这更像是软件架构的事情。这里的好处是整个程序的执行是一系列任务被中继(任务A完成->触发任务B,当任务B和任务C都完成时->触发任务D等),而不是必须编写一个大的函数或程序来一个接一个地执行每个任务。当不清楚哪些任务会比其他任务花费更多时间以及任务只是松散耦合时,这提供了灵活性。这通常通过thread-pool(等待分配任务的线程)和一些message-passing interface (MPI) 来实现,以传达数据和任务“合同”。

异步编程并不是指多线程编程,尽管两者经常关联(并且可以很好地协同工作)。 同步程序必须先完成每个步骤,然后才能进行下一步。 异步程序启动一个步骤,然后转到不需要第一步结果的其他步骤,然后在需要其结果时检查第一步的结果。

也就是说,一个同步程序可能有点像这样:“做这个任务”、“等到完成”、“用结果做某事”和“继续做其他事情”。相比之下,异步程序可能更像这样:“我要开始一个任务,稍后我需要结果,但我现在不需要它”,“与此同时,我”我会做其他事情”,“在我得到第一步的结果之前我不能做任何其他事情,所以我会等待它,如果它还没有准备好”,以及“继续做其他事情”。

请注意,“异步”指的是very broad concept,它总是涉及某种形式的“开始工作并告诉我什么时候完成”,而不是传统的“现在就做!”。这不需要多线程,在这种情况下它只是一种软件设计选择(通常涉及回调函数和类似的东西来提供异步结果的“通知”)。使用多线程,它变得更加强大,因为您可以在异步任务工作的同时并行执行各种操作。走极端,它可以成为更成熟的架构,如基于任务的方法(这是一种异步编程技术)。

我认为您想要的东西更多地对应于另一个概念:Parallel Computing(或并行处理)。这种方法更多的是把一个大的处理任务分成更小的部分,并行处理所有部分,然后组合结果。您应该查看 OpenMP 或 OpenCL/CUDA(对于 GPGPU)之类的库。也就是说,您可以使用多线程进行并行处理。

但显然异步编程并不一定意味着您使用的是多个内核?

异步编程不一定涉及在多个线程中同时发生的任何事情。这可能意味着操作系统在幕后代表您执行操作(并会在该工作完成时通知您),例如在 异步 I/O 中,这种情况在您没有创建任何线程的情况下发生。它归结为软件设计选择。

如果您没有多个内核可以利用,为什么要这样做?

如果您没有多个内核,多线程仍然可以通过重复使用“等待时间”来提高性能(例如,不要“阻塞”等待文件或网络 I/O 或等待用户的处理单击鼠标按钮)。这意味着程序可以在等待这些事情的同时做有用的工作。除此之外,它还可以在设计中提供灵活性,让事情看起来可以同时运行,这通常会让用户更开心。不过,您是正确的,在多核 CPUs 之前,没有那么多的动力去做多线程,因为收益通常不能证明开销是合理的。

【讨论】:

  • 感谢您提供最详细的回答!那么并行计算的实现真的那么复杂以至于需要一个库吗?我想象着简单地将一个非常大的音频样本数组(或几个大数组)分成 4 个片段,并使用相同函数的四个实例在不同的处理器内核上处理每个片段,然后将这四个部分再次连接在一起。在我看来,这将使处理音频的速度提高 4 倍,这非常重要,因为我的某些功能需要几分钟才能完成。是不是和很多事情一样,没有那么简单?
  • @RobbyAllsopp 就是这么简单。你是对的,它不一定需要一个库。但是像 OpenMP 这样的库(实际上,它是一个编译器扩展,大多数编译器都支持)只是让它变得更容易。看看parallel for-loop 的例子。它还可以更轻松地打开或关闭它,或者仅使用编译器选项更改线程数。
【解决方案2】:

我认为总的来说,所有这些都与设计有关,而不是与语言有关。同样适用于多核编程。

为了反映 Jim,这不仅仅是文件加载场景。通常,您需要将整个软件设计为并发运行,以便感受多线程、基于任务或异步编程的真正好处。

尝试从宏观的角度看待事物。了解特定示例的整体建模,并了解这些方法是如何实现的。很容易看出区别并帮助了解何时何地使用哪个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 2014-08-30
    相关资源
    最近更新 更多